You are here: Home ยป Blog

Some background on Generics: dumping a Map

Posted on Wednesday, December 17 2014 at 11:16 | Category: Java | 0 Comment(s)

In Java, dumping a Map to see what key/value mappings it contains is quite easy. Lets assume we have a factory method which returns a map, containing some String to Long mappings:

Map<String, Long> map = createMap();

The toString() method of the Collection classes usually provide a useful implementation, so that we can simply use

System.err.println(map);

to dump the map. If we want to have more control over the output format, e.g. one line per map entry, we can use a simple loop to iterate over the entries:

Set<Entry<String, Long>> entries = map.entrySet();
for (Entry<String, Long> entry : entries) {
    String key = entry.getKey();
    Long value = entry.getValue();
    System.err.printf("%s=%s\n", key, value);
}

When running this code with the map returned from the createMap() method, we get

Exception in thread "main" java.lang.ClassCastException: com.example.Key cannot be cast to java.lang.String
	at com.example.MapSample.run(MapSample.java:25)
	at com.example.MapSample.main(MapSample.java:54)

Ehm... wait a moment ... why do we get this exception in the line which executes String key = entry.getKey();? We did not get any compile time errors or even warnings, and entry.getKey() is declared to return a String due to the definition of entry as Entry<String, Long>. Also, the Map is declared as Map<String, Long> - so each key element in the map must be a String, right?

No.

Lets examine how the map is created in the createMap() factory method:

private Map<String, Long> createMap() {
    Map result = new HashMap();
    result.put("one",  1L);
    result.put(new Key(), 3L);
    result.put("two",  2L);
    result.put("five",  5L);
    result.put("four",  4L);
    return result;
}

Obviously, we are allowed to use different types than String as a key, an instance of the Key class in this case. This is possible since Map and HashMap are used as raw types here, instead of parameterized types. The Java compiler will issue some warnings, but will still compile the method as such (and even the warnings can be switched off with an annotation like @SuppressWarnings({ "unchecked", "rawtypes" })).

Remember that Generics (paremeterized types) are syntactic sugar only - internally, during runtime, everything is raw types, with the necessary casts automatically applied where necessary. These casts succeed when the parameterized types are used throughout the application. If this was properly done in the createMap() method, the compiler would not let us use Key as a key value. However, due to the usage of Map as raw type, we can. And then, during runtime, the casts mentioned before will fail horribly. In the example above, entry.getKey() still returns an Object reference, but due to the type parameters the compiler automatically casts the result to a String. You can verify this in the byte code which contains a corresponding checkcast instruction after entry.getKey() has been invoked.

The general rule is: do not use raw types. Especially with large legacy code bases, there are sometimes occasions where Collections are still created without the proper usage of type parameters (but later returned through parameterized types). Thus, the learning is: Even if you get a reference to a properly parameterized Collection type, do not be surprised if there are other types stored in it when you iterate over the Collection values or keys.

Back to our dumping method, we can simply solve the issue by not forcing any implicit casts:

Set<Entry<String, Long>> entries = map.entrySet();
for (Entry<String, Long> entry : entries) {
    Object key = entry.getKey();
    Object value = entry.getValue();
    System.err.printf("%s=%s\n", key, value);
}

When run, this will produce the expected output:

four=4
one=1
com.example.Key@15db9742=3
two=2
five=5

Adding a Method Call Activitiy to an ADF task flow

Posted on Monday, December 15 2014 at 12:47 | Category: Oracle ADF & JDeveloper | 0 Comment(s)

New Oracle ADF tutorial: How to add a Method Call Activitiy to a task flow. Also describes the manual approach, to get a better understanding of what happens in the background.


Incompatible Java 8 changes in the Collections runtime classes

Posted on Tuesday, December 09 2014 at 13:15 | Category: Java | 0 Comment(s)

Suppose that we want to implement a HashMap (let's call it OrderedHashMap) where the values can also retrieved by their index, where the index refers to the order in which the key/value pairs have been inserted. The following unit test shows how such a class shall be used:

@Test
public void testPutGet() {
    OrderedHashMap<String, String> ohm = new OrderedHashMap<>();
    ohm.put("Z", "LetterZ");    // 0
    ohm.put("A", "LetterA");    // 1
    ohm.put("D", "LetterD");    // 2
    ohm.put("K", "LetterK");    // 3

    assertEquals(ohm.get(0), "LetterZ");
    assertEquals(ohm.get(3), "LetterK");
}

The straight forward implementation of the class OrderedHashMap is to inherit from one of the existing Collection classes and simply implement the additional functionality, e.g. like

public class OrderedHashMap<K, V> extends LinkedHashMap<K, V> {
    private static final long serialVersionUID = 6354582314971513369L;

    private List<V> items = new ArrayList<>();

    public V get(int index) {
        return items.get(index);
    }

    @Override
    public V put(K key, V value) {
        items.add(value);
        return super.put(key, value);
    }
}

With this code, the above unit test succeeds. Lets add an additional unit tests for one of the other methods inherited from LinkedHashMap, putAll():

@Test
public void testPutAll() {
    OrderedHashMap<String, String> ohm = new OrderedHashMap<>();
    ohm.put("Z", "LetterZ");    // 0
    ohm.put("A", "LetterA");    // 1
    ohm.put("D", "LetterD");    // 2
    ohm.put("K", "LetterK");    // 3

    OrderedHashMap<String, String> ohm2 = new OrderedHashMap<&ht;();
    ohm2.putAll(ohm);

    assertEquals(ohm2.get(0), "LetterZ");
    assertEquals(ohm2.get(3), "LetterK");
}

This unit test also succeeds and shows that we can copy the elements from one OrderedHashMap to a second one, using putAll().

Unless we switch to Java 8.

If we run the above sample with the Java 8 runtime, the test fails with an exception:

java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
	at java.util.ArrayList.rangeCheck(Unknown Source)
	at java.util.ArrayList.get(Unknown Source)
	at com.example.OrderedHashMap.get(OrderedHashMap.java:13)
	at com.example.test.OrderedHashMapTest.testPutAll(OrderedHashMapTest.java:37)
	...

This is an issue I came across when I was trying to translate a grammar with ANTLR (not using the latest version) - see also https://github.com/antlr/antlr4/issues/337.

The background is that the LinkedHashMap class (or one of its super classes) changed the implementation of putAll() so that it no longer calls put() to insert the new elements in the destination map. Hence, our own implementation is no longer able to catch this call to update the List at the same time a new key/value pair is added to the map.

If you do similar things, you should check if there are such differences between the Java 7 and the Java 8 runtime which might affect your code. 

In general, this is a good example to Favor Composition over Inheritance in Java and Object Oriented Programming. The main problem is that the sub class depends on the behaviour of the superclass, and hence becomes fragile (and breaks when the superclass behaviour changes in an incompatible way). Collections are always a bad candidate for subclassing - instead, create a new class which implements the desired interfaces and then delegates to the necessary Collection classes. This is more effort at the beginning (to implement all the necessary interface methods), but makes the class much more resistent against modifications in the runtime classes. See also Effective Java, Second Edition: Item 16: Favor composition over inheritance.


Implicit default constructors

Posted on Tuesday, December 09 2014 at 08:24 | Category: Java | 0 Comment(s)

As you certainly know, if there is no constructor implemented in a class, the compiler implicitly synthesizes a default constructor. Hence, the following code compiles:

public class ClassA {

    public static void main(String[] args) {
        new ClassA();
    }
}

The disassembled code shows that there is indeed a constructor which essentially calls its super constructor, java.lang.Object.<init>:

$ javap -c ClassA.class
Compiled from "ClassA.java"
public class ClassA {
public ClassA();
    Code:
       0: aload_0
       1: invokespecial #8                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: new           #1                  // class ClassA
       3: invokespecial #16                 // Method "<init>":()V
       6: return
}

Now, there is one detail which even long-time Java developers are not always aware of: if any other constructor overload is added to the class, the compiler will not synthesize a default constructor - hence, the following code does not compile:

public class ClassA {

    public ClassA(int a) { }

    public static void main(String[] args) {
        new ClassA();
    }
}
ClassA.java:7: error: constructor ClassA in class ClassA cannot be applied to given types;
        new ClassA();
        ^
  required: int
  found: no arguments
  reason: actual and formal argument lists differ in length
1 error

This is in accordance to the Java Language Specification: 8.8.9. Default Constructor where it says:

If a class contains no constructor declarations, then a default constructor is implicitly declared.
...

Means, if there are any constructor declarations, then no default constructor will be created by the compiler. Note that there is also the same behavior in C++. Note also that the term "default constructor" as it is used by the language specification only refers to the parameterless constructor created by the compiler - hence, strictly spoken, a parameterless constructor which is explicitly implemented in the class is not a default constructor (even though developers often call it like that).

Normally, this is not an issue, since the compiler will emit the above error when we try to instantiate the class through the default constructor. However, if the object is created dynamically (e.g. through reflection), there will be no compile time error - it will become a runtime error then:

public class ClassA {

    public ClassA(int a) { }

    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        Class clazz = Class.forName("ClassA");
        ClassA obj = (ClassA) clazz.newInstance();
    }
}
Exception in thread "main" java.lang.InstantiationException: ClassA
	at java.lang.Class.newInstance(Unknown Source)
	at ClassA.main(ClassA.java:9)
Caused by: java.lang.NoSuchMethodException: ClassA.<init>()
	at java.lang.Class.getConstructor0(Unknown Source)
	... 2 more
</init>

This can be important when using persistence frameworks which do these kind of things in the background. Other examples are dependency injection frameworks which might complain at the lack of default or empty constructor, if no constructor has the @Inject annotation.


Displaying results 1 to 4 out of 98
<< First < Previous 1-4 5-8 9-12 13-16 17-20 21-24 25-28 Next > Last >>