Sunday, 20 September 2009

Item 23: Don't use raw types in new code

This is the first item of a completely new chapter in the Effective Java book 2nd edition about Generics.

The next 6 Items (23…29) deal with rules about using Generics in the right way. Generics were added to Java in Release 1.5. Essentially they allow a type or method to operate on objects of various types while providing compile-time type safety.

IIRC (this is not in the book ;-) the addition of Generics happened in 2004, and the people involved were Gilad Bracha, Martin Odersky and a few others. They wrote 2 compilers, the first was called "Pizza" and added three new features to the Java language: Generics (which is the topic here), Function pointers (first class functions ;-) and Class cases and pattern matching (algebraic types). The second compiler was called "GJ" (generic Java) and "only" contained the generic part and finally made it into the Java language and the JDK 1.5 (AFAIC remember). Some additional interesting (IMO) remarks about pizza and "GJ" at the end of this Item 23 summary. :-)

But now onto the topic: With Generics you use the compiler to "force" e.g. collections containing only a specific type. So we catch bugs earlier (at compile time instead of runtime) which is a good thing, see the code example. However there is no free lunch (™) so the rules in the book try to maximize the benefits (safety and compact code) and minimize the drawbacks (e.g. compiler warning).

// no generics
List ng = new ArrayList();
ng.add("one");
Integer i = (Integer)ng.get(0); // run time error, crash boom bang aehm
ClassCastException

// generics
List<String> g = new ArrayList();
g.add("one");
Integer i = g.get(0); // compile time error
String s = g.get(0); // correct version, no cast necessary

Joshua starts with introducing some terms from the generics are (Type, Actual/Format type Parameter, (Un)Bounded wildcard type, Raw type etc…). Please refer to table (unamed ;-) on page 115 in the book for further information, examples and corresponding Generics related Items.

Generics in Java should not be compared or confused with Templates in C++ (though they have the same angle-bracketed list of actual type parameters). While in C++ the compiler really generates new classes and code (and even can compute new things…) in Java the compiler "just" inserts casts for you automatically. Furthermore, the Java run-time environment does not even know about parameterized types/generics, because type information is validated at compile-time and erased from the compiled code (so it is backward compatible which was one major requirement when designing this language feature). I think exactly this is also the reason that there is this beast called "raw type", which is the name of the generic type WITHOUT any actual type parameters (so the raw type looks like before generics were added to the Java platform at all). example: generic: List<E> and raw type: List.

Of course using "raw type" is like programming as generics would not exist, and you get all the drawback of pre JDK 1.5 time (casting object out of collections, getting run time ClassCastExceptions, tedious debugging etc) so DON’T DO IT (this is the rule). Raw types are just there because of compatibility (migration compatibility)

Fortunately the javac compiler with option -Xlint gives you a warning when using raw types, so this minds you of this Item 23 when writing new code. e.g. "unchecked call to add(E) as a member of the raw type java.util.List" I think a small hint in the book that Item 23 IS already implemented in the std javac compiler with -Xlint would be good (you need no findbugs or other checker here).

To create a collection for arbitrary type you better use List<Object> (which
does "type checking") instead of the raw type List (which has no type checking at all). Also worth to know: there are subtyping rules for generics.

But instead of using raw types (bad) or List<Object> (a bit better), a safe and elegant alternative is to use "unbounded wildcard types" (which means List<?>). The question mark tells "I do not care or know what the actual type parameter is", but please make it type safe, I do not want to use raw types. If you need to specify some constraints about the actual type parameter, then <?> maybe too open but you can use generic methods (Item 27) or "bounded wildcard types" (Item 28) e.g. List<? extends E>, so you can enforce the actual type parameter "?" should be a subtype of E.

Because the generic type information is erased at runtime (you can check this, the javac compiler really JUST inserts the relevant casts, nothing more) we have two exceptions (or better term deviation ? ;-) where raw type actually HAVE to be used:

1. In class literals raw types must be used.
2. instanceof operator cannot query the generics type information because it was erased (not available at runtime). Hence it is useless and adding a <?> does not buy anything. Fortunately -Xlint obeys this and does not raise any noise because of using raw types here (correctly).

So summary: raw types exist because of backward compatibility. Use them only in class literals and instanceof operator. At all other locations use generics to get type errors at compile time instead of ClassCastExceptions at runtime. (A possible drawback is that you get more (or lots of) unchecked warnings which is the topic of the next Item 24).

More things (unfortunately not in the book) but IMO definetly worth to remember or know:

1. TypeErasue is happening in Java Generics. I would prefer to hear this term already in Item 23 or in the forward of Chapter 5. It is only metioned as "erasure" somewhere in Item 25. Please use "TypeErasure" Generics are "only" checked at compile-time for type correctness. The generic type information is then removed. So taking our example from the beginning the following is always true:

List<String> ls = new ArrayList<String>();
List<Integer> li = new ArrayList<Integer>();
if (ls.getClass() == li.getClass()) // evaluates to true


2. Because there is only one copy of a generic class, static variables are shared among all the instances of exactly that class, regardless of their type parameter. Effect: the type parameter cannot be used in the declaration of static variables or in static methods, e.g. the following code does not compile

class GenericLimits<T> {
private static List<T> ts; //does not compile
private static void onlyOneClass(List<T> lt) { //does not compile
}
}

3. only Reference types are accepted as types in Java Generics. We can not
use a base type like int. The following code does not compile:

List<int> li = new ArrayList<int>(); //does not compile

Yet again this was legal in the Pizza compiler.

now happy discussing, :-)

Bernhard Merkle

No comments: