Restrictions on Generics
1. Generic Types Cannot Use Primitive Types
Generics only work with reference types, not primitives.
Pair<int, char> p; // ERROR
Use wrapper classes:
Pair<Integer, Character> p = new Pair<>(8, 'a');
Java automatically converts 8 → Integer and 'a' → Character using autoboxing.
int❌ →Integer✅
2. Cannot Create an Object of a Type Parameter
You cannot do:
static <T> void create() {
T obj = new T(); // ERROR
}
Java doesn't know what T actually is at runtime.
Instead, you can pass a class:
static <T> T create(Class<T> type) throws Exception {
return type.getDeclaredConstructor().newInstance();
}
Use:
String s = create(String.class);
3. Cannot Use Type Parameters for Static Fields
This is not allowed:
class Box<T> {
static T value; // ERROR
}
Why?
static belongs to the class, but T can be different for each object/type:
Box<String>
Box<Integer>
Java wouldn't know which T the static field should use.
Instance fields are fine:
class Box<T> {
T value; // OK
}
4. Cannot Use instanceof with Specific Generic Types
This is not allowed:
if (list instanceof List<String>) { // ERROR
}
Because generic type information is erased at runtime.
Java cannot distinguish:
List<String>
List<Integer>
You can use an unbounded wildcard:
if (list instanceof List<?>) { // OK
}
5. Cannot Create Arrays of Parameterized Types
This is not allowed:
List<String>[] lists = new List<String>[10]; // ERROR
Because arrays know their element type at runtime, but generic type arguments are erased.
You can create:
List<?>[] lists = new List<?>[10]; // OK
6. Generic Classes Cannot Extend Throwable
You cannot create a generic exception:
class MyException<T> extends Exception { } // ERROR
Java also doesn't allow:
catch (T e) { } // ERROR
However, a type parameter can appear in throws:
class Parser<T extends Exception> {
void parse() throws T {
// ...
}
}
7. Cannot Overload Methods with the Same Erasure
This is not allowed:
void print(List<String> list) { }
void print(List<Integer> list) { } // ERROR
Why?
Because after type erasure, both become:
void print(List list)
So Java sees two methods with the same signature.
You can instead use different method names:
void printStrings(List<String> list) { }
void printIntegers(List<Integer> list) { }
Quick Revision
| Restriction | Example |
|---|---|
| No primitive type arguments | List<int> ❌ |
Can't create T directly |
new T() ❌ |
No static field of type T |
static T value ❌ |
No specific generic instanceof |
x instanceof List<String> ❌ |
| No generic arrays | new List<String>[10] ❌ |
No generic Throwable |
class E<T> extends Exception ❌ |
No catch(T) |
catch(T e) ❌ |
| No same-erasure overloads | print(List<String>) + print(List<Integer>) ❌ |
Easy way to remember
Most of these restrictions exist because of type erasure:
Java knows generic types at compile time, but most generic type information is gone at runtime.