Restrictions of Generics in Java
Generics have a short list of things they can't do, and they all have the same root cause: type erasure (see "Type Erasure in Java"). Because the JVM doesn't know what T is at runtime, anything that needs that knowledge while the program runs is impossible. Each restriction comes with a standard workaround, so none of them is a real dead end once you know it.
The Errors
import java.util.List;
class Rest<T> {
T make() { return new T(); }
T[] arr() { return new T[10]; }
static T shared;
boolean check(Object o) { return o instanceof List<String>; }
List<int> nums;
}Rest.java:6: error: non-static type variable T cannot be referenced from a static context
static T shared;
^
Rest.java:8: error: unexpected type
List<int> nums;
^
required: reference
found: int
Rest.java:4: error: unexpected type
T make() { return new T(); }
^
required: class
found: type parameter T
where T is a type-variable:
T extends Object declared in class Rest
Rest.java:5: error: generic array creation
T[] arr() { return new T[10]; }
^
Rest.java:7: error: Object cannot be safely cast to List<String>
boolean check(Object o) { return o instanceof List<String>; }
^
5 errorsTwo more, from separate files:
Over.java:4: error: name clash: print(List<Integer>) and print(List<String>) have the same erasure
void print(List<Integer> b) { }
^
Ex.java:1: error: a generic class may not extend java.lang.Throwable
class MyEx<T> extends Exception { }
^Each Restriction, Its Reason and Its Workaround
| You can't... | Because | Instead |
|---|---|---|
new T() | The runtime doesn't know which class to create | Pass a Supplier<T> or a Class<T> |
new T[10] | Arrays must know their element type at runtime | Use a List<T>, or Array.newInstance(type, n) |
Use T in static fields or methods | One class is shared by every Box<...> | Make the static method generic: static <U> ... |
instanceof List<String> | The <String> part doesn't exist at runtime | instanceof List<?>, then check elements |
List<int> | Type arguments must be objects | List<Integer> (autoboxing) |
| Overload on type arguments only | Both erase to the same signature | Use different method names |
| Make an exception class generic | catch needs the exact type at runtime | Store extra data in fields instead |
catch (T e) | Same reason | Catch a specific type |
The Workarounds in Action
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;
public class Workarounds {
static <T> List<T> fill(Supplier<T> maker, int n) {
List<T> out = new ArrayList<>();
for (int i = 0; i < n; i++) {
out.add(maker.get());
}
return out;
}
static <T> T create(Class<T> type) throws Exception {
return type.getDeclaredConstructor().newInstance();
}
static boolean isList(Object o) {
return o instanceof List<?>;
}
public static void main(String[] args) throws Exception {
System.out.println(fill(() -> "Navika", 3));
StringBuilder sb = create(StringBuilder.class);
System.out.println(sb.append("made by reflection"));
System.out.println(isList(List.of(1, 2)) + " " + isList("Advitha"));
}
}[Navika, Navika, Navika]
made by reflection
true falseHow it works
Supplier<T>: the caller passes a small function,() -> "Navika", that knows how to make aT. This is the cleanest replacement fornew T().Class<T>: passingStringBuilder.classgives the method a runtime type to create through reflection. This "type token" pattern is common in frameworks.instanceof List<?>is allowed, because the unbounded wildcard makes no claim about the elements.
Common Mistakes
Fighting erasure with unchecked casts like (T[]) new Object[n]. It compiles with a warning, but can fail later if the array escapes.
Overloading process(List<Order>) and process(List<Invoice>). Name them processOrders and processInvoices.
Making a custom exception generic to carry typed data. Add a field of the right type instead.
Interview Corner
What are the restrictions on generics in Java?
You can't use primitive type arguments, create instances or arrays of a type parameter, declare static fields of a type parameter, use instanceof or casts with parameterized types (except unbounded wildcards), overload methods whose parameters erase to the same type, create generic subclasses of Throwable, or catch a type parameter. All follow from type erasure, since the runtime doesn't know the type arguments.
How can you create an instance of T in a generic class?
Pass the information in. Either accept a Supplier<T> and call get(), or accept a Class<T> and create the instance with reflection, type.getDeclaredConstructor().newInstance(). A supplier is simpler and type-safe; a class token is common in frameworks.
Practice
- Easy: Rewrite
T make() { return new T(); }in a genericFactory<T>class so it uses aSupplier<T>passed to the constructor. - Harder: Write a generic
Matrix<T>that stores values in aList<List<T>>instead of aT[][], withget(row, col)andset(row, col, value).
Key Takeaways
Was this tutorial helpful?