🧪 Generics · Intermediate

Generic restrictions in Java

No primitives, no new T(), no T[] creation, no instanceof List<String>, no static T fields.

🧩 The mysterynew 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();
🔮 Predict it

Your turn

What happens?

class Shelf<T> {
    T[] items = new T[4];
}
void main() {
    System.out.println("ok");
}
  1. Prints ok
  2. Compile error
  3. Throws 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<?>      // ✓
🔮 Predict it

A checkable pattern

What does this print?

Object o = List.of(1, 2, 3);
if (o instanceof List<?> list) {
    System.out.println(list.size());
}
  1. 3
  2. Compile error
  3. 0
Show the answer

List<?> needs no element check, so the pattern match is legal. o really is a list of 3 elements: 3.

⚠️ The trap

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
}
💼 In the real world

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

  1. No List<int> — use List<Integer> or IntStream
  2. No new T() — pass a Supplier<T> or a Class<T>
  3. No new T[n] — use a List<T> or pass an array/IntFunction
  4. No static T field — statics are shared by every Box<X>
🤯 Did you know?

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");
}
  1. ok
  2. Compile error
  3. Throws ArrayStoreException
  4. 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?

  1. Write new T() and add @SuppressWarnings
  2. Accept a Supplier<T> (e.g. User::new) in the constructor and call get()
  3. Cast: (T) new Object()
  4. 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.

Next: what happens when you use a generic type with NO type arguments at all? The answer is called heap pollution.