Restrictions on Generics
To use Java generics effectively, you must consider the following restrictions.
Cannot Instantiate Generic Types with Primitive Types
Consider the following parameterized type:
class Pair<K, V> { private K key; private V value; public Pair(K key, V value) { this.key = key; this.value = value; } // . . . }
When creating a Pair object, you cannot substitute a primitive type for the type parameter K or V:
Pair<int, char> p = new Pair<>(8, 'a'); // compile-time error
You can only substitute nonprimitive types for the type parameters K and V:
Pair<Integer, Character> p = new Pair<>(8, 'a');
Note that the Java compiler autoboxes 8 to Integer.valueOf(8) and 'a' to Character('a'):
Pair<Integer, Character> p = new Pair<>(Integer.valueOf(8), new Character('a'));
For more information on autoboxing, see Chapter 8.
Cannot Create Instances of Type Parameters
You cannot create an instance of a type parameter. For example, the following code causes a compile-time error:
public static <E> void append(List<E> list) { E elem = new E(); // compile-time error list.add(elem); }
As a workaround, you can create an object of a type parameter through reflection:
public static <E> void append(List<E> list, Class<E> cls) throws Exception { E elem = cls.newInstance(); // OK list.add(elem); }
You can invoke the append method as follows:
List<String> ls = new ArrayList<>(); append(ls, String.class);
Cannot Declare Static Fields Whose Types Are Type Parameters
A class’s static field is a class-level variable shared by all nonstatic objects of the class. Hence, static fields of type parameters are not allowed. Consider the following class:
public class MobileDevice<T> { private static T os; // . . . }
If static fields of type parameters were allowed, then the following code would be confused:
MobileDevice<Smartphone> phone = new MobileDevice<>(); MobileDevice<Pager> pager = new MobileDevice<>(); MobileDevice<TabletPC> pc = new MobileDevice<>();
Because the static field os is shared by phone, pager, and pc, what is the actual type of os? It cannot be Smartphone, Pager, and TabletPC at the same time. You cannot, therefore, create static fields of type parameters.
Cannot Use Casts or instanceof with Parameterized Types
Because the Java compiler erases all type parameters in generic code, you cannot verify which parameterized type for a generic type is being used at runtime:
public static <E> void rtti(List<E> list) { if (list instanceof ArrayList<Integer>) { // compile-time error // . . . } }
The set of parameterized types passed to the rtti method is as follows:
S = { ArrayList<Integer>, ArrayList<String> LinkedList<Character>, . . . }
The runtime does not keep track of type parameters, so it cannot tell the difference between an ArrayList<Integer> and an ArrayList<String>. The most you can do is to use an unbounded wildcard to verify that the list is an ArrayList:
public static void rtti(List<?> list) { if (list instanceof ArrayList<?>) { // OK; instanceof requires a // reifiable type // . . . } }
Typically, you cannot cast to a parameterized type unless it is parameterized by unbounded wildcards. Here is an example:
List<Integer> li = new ArrayList<>(); List<Number> ln = (List<Number>) li; // compile-time error
However, in some cases, the compiler knows that a type parameter is always valid and allows the cast. Here’s an example:
List<String> l1 = . . . ; ArrayList<String> l2 = (ArrayList<String>)l1; // OK
Cannot Create Arrays of Parameterized Types
You cannot create arrays of parameterized types. For example, the following code does not compile:
List<Integer>[] arrayOfLists = new List<Integer>[2]; // compile-time error
The following code illustrates what happens when different types are inserted into an array:
Object[] strings = new String[2]; strings[0] = "hi"; // OK strings[1] = 100; // An ArrayStoreException is thrown.
If you try the same thing with a generic list, there would be a problem:
Object[] stringLists = new List<String>[]; // compiler error, but pretend // it's allowed stringLists[0] = new ArrayList<String>(); // OK stringLists[1] = new ArrayList<Integer>(); // An ArrayStoreException // should be thrown, // but the runtime can't detect it.
If arrays of parameterized lists were allowed, the previous code would fail to throw the desired ArrayStoreException.
Cannot Create, Catch, or Throw Objects of Parameterized Types
A generic class cannot extend the Throwable class directly or indirectly. For example, the following classes will not compile:
// Extends Throwable indirectly class MathException<T> extends Exception { /* . . . */ } // compile-time error // Extends Throwable directly class QueueFullException<T> extends Throwable { /* . . . */ // compile-time error
A method cannot catch an instance of a type parameter:
public static <T extends Exception, J> void execute(List<J> jobs) { try { for (J job : jobs) // . . . } catch (T e) { // compile-time error // . . . } }
You can, however, use a type parameter in a throws clause:
class Parser<T extends Exception> { public void parse(File file) throws T { // OK // . . . } }
Cannot Overload a Method Where the Formal Parameter Types of Each Overload Erase to the Same Raw Type
A class cannot have two overloaded methods that will have the same signature after type erasure:
public class Example { public void print(Set<String> strSet) { } public void print(Set<Integer> intSet) { } }
The overloads would all share the same classfile representation, which will generate a compile-time error.