Generic restrictions in Java
No primitives, no new T(), no T[] creation, no instanceof List<String>, no static T fields.
new T() won't compile. new T[10] won't compile. List<int> won't compile. These aren't random rules: they all follow from one fact you just learned.Four things T can't do
Because T is erased and type arguments must be reference types, a few things are impossible. Each has a standard workaround.
List<int> a; // ✗ primitive
T t = new T(); // ✗ no constructor
T[] arr = new T[10]; // ✗ generic array
static T shared; // ✗ static field
o instanceof List<String> // ✗ (o is Object)No primitives
Type arguments must be reference types. Use the wrapper List<Integer> (each value is boxed), or primitive-friendly APIs like IntStream and int[] when performance matters.
List<Integer> nums = List.of(1, 2, 3);
int total = IntStream.of(1, 2, 3).sum();No new T()
At runtime T is just Object, so there's no known constructor to call. The caller knows the real type, so let it pass a **Supplier<T>**, such as a constructor reference User::new.
class Factory<T> {
private final Supplier<T> maker;
Factory(Supplier<T> m) { maker = m; }
T create() { return maker.get(); }
}
new Factory<>(StringBuilder::new).create();Your turn
What happens?
class Shelf<T> {
T[] items = new T[4];
}
void main() {
System.out.println("ok");
}Prints okCompile errorThrows ArrayStoreException
Show the answer
Generic array creation is illegal. Arrays must know their real element type at runtime, but T has been erased. Use a List<T> instead.
instanceof and generics
At runtime there's no element type to check, so **o instanceof List<String> on a plain Object doesn't compile. But List<?>** promises nothing about elements, so it's checkable ("reifiable") and allowed.
Object o = List.of("a");
o instanceof List<String> // ✗ can't check
o instanceof List<?> // ✓A checkable pattern
What does this print?
Object o = List.of(1, 2, 3);
if (o instanceof List<?> list) {
System.out.println(list.size());
}3Compile error0
Show the answer
List<?> needs no element check, so the pattern match is legal. o really is a list of 3 elements: 3.
No static T
Box<String> and Box<Integer> share one class and therefore one set of static fields. A static T would have to be a String and an Integer at once, so it's forbidden.
class Box<T> {
static T last; // ✗ compile error
static int count; // ✓ shared by every Box
}Workarounds in the wild
Repositories and factories take a Supplier<T> or Class<T>. The JDK's own list.toArray(String[]::new) (Java 11+) passes in an array-making function precisely because toArray can't write new T[n] itself.
Key takeaways
- No List<int> — use List<Integer> or IntStream
- No new T() — pass a Supplier<T> or a Class<T>
- No new T[n] — use a List<T> or pass an array/IntFunction
- No static T field — statics are shared by every Box<X>
The classic list.toArray(new String[0]) passes an empty array just so the method can learn the element type at runtime and create a correctly typed array to return.
Practice questions
What does this print?
class Box<T> {
T[] items = new T[10];
}
void main() {
System.out.println("ok");
}- ok
- Compile error
- Throws ArrayStoreException
- Throws ClassCastException
Check your answer
Compile error. Generic array creation is illegal: arrays must know their element type at runtime, but T is erased. Use a List<T> instead.
A generic Repository<T> needs to create new T instances. Which approach works?
- Write new T() and add @SuppressWarnings
- Accept a Supplier<T> (e.g. User::new) in the constructor and call get()
- Cast: (T) new Object()
- Call T.class.getDeclaredConstructor().newInstance()
Check your answer
Accept a Supplier<T> (e.g. User::new) in the constructor and call get(). The caller knows the real type, so it passes a constructor reference. new T() and T.class don't compile, and (T) new Object() compiles with a warning but produces a plain Object that fails later.