February 07, 2005

Java 5 Collections inconsistencies

Generics are one of the Cool New Features™ in Java 5. The collections framework has been ported to make use of the new syntax and semantics. So now it's possible to do the following:

List<String> list = new Array<String>();

list.add("hello");
list.add("world");

String hello = list.get(0);
String world = list.get(1);

for (String str : list) {
     System.out.println(str.length());
}


There are definite advantages to the generics-enabled syntax. It looks *much* cleaner for starters. And we can combine them with the new for each syntax for a nicer looking loop construct. Compare the above with pre-Java 5 code:

List list = new ArrayList();

list.add("hello");
list.add("world");

String hello = (String) list.get("hello"); // downcast needed here!
String world = (String) list.get("world");

// this is plain ugly...
Iterator i = list.iterator();
while (i.hasNext()) {
     String element = (String) e.next();
     System.out.println(element.length());
}


But a couple of aspects of the new generics-enabled collections framework annoy me. For example, the Collections interface is declared as Collection<E> and the add method, for example, is correctly declared as:

boolean add(E object)

So I cannot help but wonder why, then, is remove declared as:

boolean remove(Object object)

This declaration makes it possible to call remove with an element of the wrong type. While it won't trigger an exception, it could hide a coding error. For example, in the snippet below we "forget" to call toString() on x. Yet, we don't get a compilation or runtime error. We can only realize there's a mistake when the program does not produce the expected results.


List<String> list = new ArrayList<String>();

list.add("hello");
list.add("1");
...
Integer x = new Integer(1);
list.remove(x);


Similarly, the toArray() method should be declared as follows:

E[] toArray()

Instead of:

Object[] toArray()

Othewise, we need to downcast the result of the call, as shown in the example below... yuck! But most importantly, it allows invalid down casts, which is one of the things generics try to help avoid in the first place.

List list = new ArrayList();

list.add("hello");

Object[] elements = list.toArray();

String string = (String) elements[0]; // downcast needed
Integer intValue = (Integer) elements[0]; // accepted by compiler, but fails at runtime.


Lastly, there's another variation of the toArray method, which has been defined as:

<T> T[] toArray(T[] type)

Here the method uses generics, but there's a caveat. If T is not a supertype of E, an ArrayStoreException will be thrown at runtime. It would be nice to be able to capture the relationship between T and E as in the example below. Unfortunately, such syntax is not allowed:

<T super E> T[] toArray(T[] type)

I don't know what were the reasons behind these choices, and I certainly hope it wasn't a case of "oops, I missed it"-itis. For performance, maybe (the compiler would save a downcast)? If that was it, it wasn't a good trade-off, IMO.

Update: fixed an error in one of the code snippets. Thanks for the observation, Diego.

2 Comments:

Blogger Eugene Kuleshov said...

Some internals for this issue at http://www.jroller.com/page/eu/20050209#bytecode_tales_erasure_of_collection and http://www.jroller.com/page/eu/20050209#a_non_intrusive_way_to

February 08, 2005 10:50 PM  
Anonymous Anonymous said...

Cell phones from simple communication became a full-featured entertainment terminal . Can be said that cell phone china new milestone in the industry. This fully demonstrates that, cheap cell phones entertainment has become the most sought after consumer applications and has become the focus of the mobil phones market.

September 24, 2009 9:27 PM  

Post a Comment

<< Home