Previous in the Series
Current Tutorial
Restriction on Generics
That's the end of the series!

Previous in the Series: Type Erasure

Restriction on Generics

 

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 substitute only non-primitive 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 Autoboxing and Unboxing in the Numbers and Strings section.

 

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 non-static 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:

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. For 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. For 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>[2];  // 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 and will generate a compile-time error.


Last update: September 14, 2021


Previous in the Series
Current Tutorial
Restriction on Generics
That's the end of the series!

Previous in the Series: Type Erasure