1. The Hook (The "Byte-Sized" Intro)
In a Nutshell: Generics have 7 major restrictions due to type erasure: (1) Cannot instantiate type parameters (new T()), (2) Cannot create generic arrays (new T[]), (3) Cannot use primitives (List<int>), (4) Cannot use static generic fields, (5) Cannot catch generic exceptions, (6) Cannot instanceof with generics, (7) Cannot overload methods with same erasure. All stem from type erasureβtypes don't exist at runtime!
Think of ghost in a photo. Generics = visible during development (compile-time), invisible in final product (runtime). Can't interact with invisible things!
2. Conceptual Clarity (The "Simple" Tier)
π‘ The Analogy: The Blueprint vs Building
Generics = blueprint annotations (compile-time). Final building has no annotations (runtime). Can't build from erased info!
3. Technical Mastery (The "Deep Dive")
The 7 Restrictions
| Restriction | Why? | Workaround |
|---|---|---|
Cannot new T() | Type erased | Pass Class<T>, use Factory |
Cannot new T[] | Arrays store type, but T erased | Use ArrayList<T> |
Cannot List<int> | Primitives aren't objects | Use List<Integer> |
Cannot static T field | Static shared across all types | Use static generic methods |
Cannot catch E extends Exception | Catch clause needs runtime type | Use type parameter differently |
Cannot instanceof List<String> | Type info erased | Use raw type check |
| Cannot overload same erasure | Both erase to same signature | Use different method names |
4. Interactive & Applied Code
import java.util.*;
public class GenericRestrictionsDemo {
// β RESTRICTION 1: Cannot instantiate type parameter
static class Box<T> {
// β private T instance = new T(); // Compile error!
// β
WORKAROUND 1: Factory pattern
private T instance;
public static <T> Box<T> create(Class<T> clazz) throws Exception {
Box<T> box = new Box<>();
box.instance = clazz.getDeclaredConstructor().newInstance();
return box;
}
// β
WORKAROUND 2: Supplier
private T instance2;
public Box(java.util.function.Supplier<T> supplier) {
this.instance2 = supplier.get();
}
}
// β RESTRICTION 2: Cannot create generic arrays
static class Container<T> {
// β private T[] array = new T[10]; // Compile error!
// β
WORKAROUND: Use ArrayList
private List<T> list = new ArrayList<>();
// β
WORKAROUND: Use Object[] + cast (unsafe!)
@SuppressWarnings("unchecked")
private T[] array = (T[]) new Object[10];
}
// β RESTRICTION 3: Cannot use primitives
static void primitiveExample() {
// β List<int> numbers = new ArrayList<>(); // Compile error!
// β
Use wrapper classes
List<Integer> numbers = new ArrayList<>();
numbers.add(5); // Autoboxing
int x = numbers.get(0); // Unboxing
}
// β RESTRICTION 4: Cannot have static generic field
static class StaticExample<T> {
// β private static T value; // Compile error!
private T instanceValue; // β
Instance field OK
// β
Static generic METHOD is OK (has own <T>)
public static <T> List<T> createList(T item) {
List<T> list = new ArrayList<>();
list.add(item);
return list;
}
}
// β RESTRICTION 5: Cannot catch generic exception
static class ExceptionExample<E extends Exception> {
public void method() {
try {
throw new Exception();
}
// β catch (E e) { } // Compile error!
catch (Exception e) { // β
Catch concrete type
System.out.println(e);
}
}
}
// β RESTRICTION 6: Cannot instanceof with generics
static void instanceofExample() {
List<String> strings = new ArrayList<>();
// β if (strings instanceof List<String>) {} // Compile error!
// β
Use raw type
if (strings instanceof List) { // OK
System.out.println("Is a List");
}
}
// β RESTRICTION 7: Cannot overload with same erasure
static class OverloadExample {
// Both erase to process(List)
// β public void process(List<String> list) {}
// β public void process(List<Integer> list) {} // Compile error!
// β
Use different names
public void processStrings(List<String> list) {}
public void processIntegers(List<Integer> list) {}
}
public static void main(String[] args) throws Exception {
// Demonstrate workarounds
Box<String> box = Box.create(String.class);
System.out.println("Box created via reflection");
Box<String> box2 = new Box<>(() -> "Default value");
System.out.println("Box created via Supplier");
primitiveExample();
instanceofExample();
}
}5. The Comparison & Decision Layer
Most Common Restrictions
6. The "Interview Corner" (The Edge)
The "Killer" Interview Question:
"Why can't you have private static T value in a generic class?"
Answer: Static members are shared across ALL instances of the class, but T is determined per instance:
class Box<T> {
// β If this worked:
// private static T value;
}
Box<String> stringBox = new Box<>(); // T = String
Box<Integer> intBox = new Box<>(); // T = Integer
// What would Box.value be? String or Integer?
// BOTH classes share the same static field!Static fields can't have instance-specific types!
Pro-Tip: @SafeVarargs for generic varargs:
// Generic varargs creates array internally
@SafeVarargs // Suppresses heap pollution warning
public static <T> List<T> asList(T... elements) {
List<T> list = new ArrayList<>();
for (T element : elements) {
list.add(element);
}
return list;
}
// Usage
List<String> list = asList("A", "B", "C");Only use @SafeVarargs if you're SURE the method is safe!