Helping ordinary people create extraordinary websites!
HOME TUTORIALS SCRIPTS WEB HOSTING BLOG FORUM
Get Our Newsletter
Email:

Java Theory and Practice: Generics Gotchas

By Brian Goetz
2005-03-23


Generifying existing classes

Converting existing library classes to work smoothly with generics was no small feat, but as is often the case, backward-compatibility does not come for free. I've already talked about two examples where backward compatibility limited the generification of the class libraries.

Another method that would probably have been generified differently had backward compatibility not been an issue is Collections.toArray(Object[]). The array passed into toArray() serves two purposes -- if the collection is small enough to fit in the array provided, the contents will simply be placed in that array. Otherwise, a new array of the same type will be created, using reflection, to receive the results. If the Collections framework were being rewritten from scratch, it is likely that the argument to Collections.toArray() would not be an array, but instead a class literal:

interface Collection<E> { 

public T[] toArray(Class<T super E> elementClass);
}


Because the Collections framework is widely emulated as an example of good class design, it is worth noting the areas in which its design was constrained by backwards compatibility, so that those aspects are not emulated blindly.

One element of the generifed Collections API that is often confusing at first is the signatures of containsAll(), removeAll(), and retainAll(). You might expect the signatures for remove() and removeAll() to be:

interface Collection<E> { 

public boolean remove(E e); // not really
public void removeAll(Collection<? extends E> c); // not really
}


But it is in fact:

interface Collection<E> { 

public boolean remove(Object o);
public void removeAll(Collection<?> c);
}


Why is this? Again, the answer lies in backward compatibility. The interface contract of x.remove(o) means "if o is contained in x, remove it; otherwise, do nothing." If x is a generic collection, o does not have to be type-compatible with the type parameter of x. If removeAll() were generified to only be callable if its argument was type-compatible (Collection<? extends E>), then certain sequences of code that were legal before generics would become illegal, like this one:

// a collection of Integers
Collection c = new HashSet();
// a collection of Objects
Collection r = new HashSet();
c.removeAll(r);


If the above fragment were generified in the obvious way (making c a Collection<Integer> and r a Collection<Object>), then the code above would not compile if the signature of removeAll() required its argument to be a Collection<? extends E>, instead of being a no-op. One of the key goals of generifying the class libraries was to not break or change the semantics of existing code, so remove(), removeAll(), retainAll(), and containsAll() had to be defined with a weaker type constraint than they might have had they been redesigned from scratch for generics.

Existing classes that were designed before generics may have semantics that resist the "obvious" generification approach. In these cases, you will probably make compromises like the ones described here, but when designing new generic classes from scratch, it is valuable to understand which idioms from the Java class libraries are the result of backward compatibility, so they are not emulated inappropriately.

Tutorial Pages:
» Identify and avoid some of the pitfalls in learning to use generics
» Generics are not covariant
» Construction delays
» Generifying existing classes
» Implications of erasure
» Summary
» Resources


First published by IBM developerWorks


 | Bookmark
Related Tutorials:
» All about JAXP, Part 1
» Make Database Queries Without the Database
» Load List Values for Improved Efficiency
» 2 Ways To Implement Session Tracking
» A Simple Way to Read an XML File in Java
» Develop Aspect-Oriented Java Applications with Eclipse and AJDT