Saturday 7 July 2012

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


A class or interface whose declaration has one or more type parameters is a generic class or interface [JLS, 8.1.2, 9.1.2]. For example, as of release 1.5, the List interface has a single type parameter, E, representing the element type of the list. Technically the name of the interface is now List<E> (read “list of E”), but people often call it List for short. Generic classes and interfaces are collectively known as generic types.

Each generic type defines a raw type, which is the name of the generic type used without any accompanying actual type parameters [JLS, 4.8]. For example, the raw type corresponding to List<E> is List.

Before release 1.5, this would have been an exemplary collection declaration:

// Now a raw collection type - don't do this!
/**
* My stamp collection. Contains only Stamp instances.
*/
private final Collection stamps = ... ;

If you accidentally put a coin into your stamp collection, the erroneous insertion compiles and runs without error:

// Erroneous insertion of coin into stamp collection
stamps.add(new Coin( ... ));

You don’t get an error until you retrieve the coin from the stamp collection:

// Now a raw iterator type - don't do this!
for (Iterator i = stamps.iterator(); i.hasNext(); ) {
Stamp s = (Stamp) i.next(); // Throws ClassCastException
... // Do something with the stamp
}

It pays to discover errors as soon as possible after they are made, ideally at compile time.

With generics, you replace the comment with an improved type declaration for the collection that tells the compiler the information that was previously hidden in the comment:

// Parameterized collection type - typesafe
private final Collection<Stamp> stamps = ... ;

When stamps is declared with a parameterized type, the erroneous insertion generates a compile-time error message that tells you exactly what is wrong:

Test.java:9: add(Stamp) in Collection<Stamp> cannot be applied
to (Coin)
stamps.add(new Coin());
^

As an added benefit, you no longer have to cast manually when removing elements from collections.

It had to be legal to pass instances of parameterized types to methods that were designed for use with ordinary types, and vice versa. This requirement, known as migration compatibility, drove the decision to support raw types.

You lose type safety if you use a raw type like List, but not if you use a parameterized type like List<Object>.

To make this concrete, consider the following program:

// Uses raw type (List) - fails at runtime!
public static void main(String[] args) {
List<String> strings = new ArrayList<String>();
unsafeAdd(strings, new Integer(42));
String s = strings.get(0); // Compiler-generated cast
}
private static void unsafeAdd(List list, Object o) {
list.add(o);
}

This program compiles, but because it uses the raw type List, you get a warning:

Test.java:10: warning: unchecked call to add(E) in raw type List
list.add(o);
^

And indeed, if you run the program, you get a ClassCastException when the program tries to cast the result of the invocation strings.get(0) to a String.

If you replace the raw type List with the parameterized type List<Object> in the unsafeAdd declaration and try to recompile the program, you’ll find that it no longer compiles. Here is the error message:

Test.java:5: unsafeAdd(List<Object>,Object) cannot be applied
to (List<String>,Integer)
unsafeAdd(strings, new Integer(42));
^

Since release 1.5, Java has provided a safe alternative known as unbounded wildcard types. If you want to use a generic type but you don’t know or care what the actual type parameter is, you can use a question mark instead. For example, the unbounded wildcard type for the generic type Set<E> is Set<?> (read “set of some type”). It is the most general parameterized Set type, capable of holding any set.

// Unbounded wildcard type - typesafe and flexible
static int numElementsInCommon(Set<?> s1, Set<?> s2) {
int result = 0;
for (Object o1 : s1)
if (s2.contains(o1))
result++;
return result;
}

you can’t put any element (other than null) into a Collection<?>. Attempting to do so will generate a compile-time error message like this:

WildCard.java:13: cannot find symbol
symbol : method add(String)
location: interface Collection<capture#825 of ?>
c.add("verboten");
^

You must use raw types in class literals. The specification does not permit the use of parameterized types (though it does permit array types and primitive types) [JLS, 15.8.2]. In other words, List.class, String[].class, and int.class are all legal, but List<String>.class and List<?>.class are not.

This is the preferred way to use the instanceof operator with generic types:

// Legitimate use of raw type - instanceof operator
if (o instanceof Set) { // Raw type
Set<?> m = (Set<?>) o; // Wildcard type
...
}

In summary, using raw types can lead to exceptions at runtime, so don’t use them in new code. They are provided only for compatibility and interoperability with legacy code that predates the introduction of generics. As a quick review, Set<Object> is a parameterized type representing a set that can contain objects of any type, Set<?> is a wildcard type representing a set that can contain only objects of some unknown type, and Set is a raw type, which opts out of the generic type system. The first two are safe and the last is not.

Reference: Effective Java 2nd Edition by Joshua Bloch