Restrictions of Generics in Java

Naveen AnamaneniUpdated · 5 min read

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

Rest.java
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;
}
Compiler output
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 errors

Two more, from separate files:

Compiler output
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...BecauseInstead
new T()The runtime doesn't know which class to createPass a Supplier<T> or a Class<T>
new T[10]Arrays must know their element type at runtimeUse a List<T>, or Array.newInstance(type, n)
Use T in static fields or methodsOne class is shared by every Box<...>Make the static method generic: static <U> ...
instanceof List<String>The <String> part doesn't exist at runtimeinstanceof List<?>, then check elements
List<int>Type arguments must be objectsList<Integer> (autoboxing)
Overload on type arguments onlyBoth erase to the same signatureUse different method names
Make an exception class genericcatch needs the exact type at runtimeStore extra data in fields instead
catch (T e)Same reasonCatch a specific type

The Workarounds in Action

Workarounds.java▶ Try it
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"));
    }
}
Output
[Navika, Navika, Navika]
made by reflection
true false

How it works

  1. Supplier<T>: the caller passes a small function, () -> "Navika", that knows how to make a T. This is the cleanest replacement for new T().
  2. Class<T>: passing StringBuilder.class gives the method a runtime type to create through reflection. This "type token" pattern is common in frameworks.
  3. 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

  1. Easy: Rewrite T make() { return new T(); } in a generic Factory<T> class so it uses a Supplier<T> passed to the constructor.
  2. Harder: Write a generic Matrix<T> that stores values in a List<List<T>> instead of a T[][], with get(row, col) and set(row, col, value).

Quick check: Restrictions

4 questions
1Why can't you write new T()?
2Which instanceof check compiles?
3Why can't print(List<String>) and print(List<Integer>) coexist?
4Can an exception class be generic?

Key Takeaways

Was this tutorial helpful?

Lesson 251 of 482