Code | Valid? | Reason |
---|---|---|
class GenericClass<T> implements GenericInterface<T> | Yes | Generic class implements generic interface with the same type parameter. |
class NormalClass implements GenericInterface<T> | No | Non-generic class cannot use generic type parameter T . |
class NormalClass implements GenericInterface<Integer> | Yes | Non-generic class specifies a concrete type (Integer ). |
class class0 implements Iinterface<Integer> | Yes | Non-generic class implements generic interface with a concrete type. |
class class5<T> extends class4<T> implements Iinterface<Integer> | No | Inconsistent type usage (generic T vs. concrete Integer ). |
The compiler looks at the left-hand side of the assignment (the variable declaration) to determine the type.
It then applies that type to the right-hand side (the object instantiation) where the diamond operator is used.
The left-hand side is List<String> list
, so the compiler knows the type is String
.
The right-hand side is new ArrayList<>()
, so the compiler infers that the ArrayList
should also be of type String
.
Subtyping
List extends Number> numbers = new ArrayList< Integer>();
Number num = numbers.get(0); // Valid: Reading is allowed
// numbers.add(10); // Invalid: Cannot add to a list of unknown subtype
List super Integer> numbers = new ArrayList< Number>();
numbers.add(10); // Valid: Adding is allowed
// Integer num = numbers.get(0); // Invalid: Reading returns Object
Object obj = numbers.get(0); // Valid: Reading as Object
List < Integer> numbers = new ArrayList<>();
numbers.add(10); // Valid: Adding is allowed
Integer num = numbers.get(0); // Valid: Reading is allowed
Generics in Java are implemented using a mechanism called type erasure. This approach ensures that generic code is backward-compatible with legacy code that uses raw types. However, it also imposes certain restrictions on how generics can be used
At Compile Time:
The compiler uses generic type information to enforce type safety.
It checks that the code adheres to the generic type constraints (e.g., you can’t add a String
to a List<Integer>
).
At Runtime:
The generic type information is erased (removed).
All generic types are replaced with their raw types (e.g., List<T>
becomes List
).
Type parameters are replaced with their upper bound (e.g., T
becomes Object
if no explicit bound is specified).
Cannot Instantiate Generic Types with Primitive Types
Generic type parameters must be reference types (e.g., Integer
, String
).
You cannot use primitive types (e.g., int
, char
) as type arguments.
// List< int> list = new ArrayList <>(); // Invalid
List< Integer> list = new ArrayList<>(); // Valid
class Box< T> {
T createInstance() {
// return new T(); // Invalid: Cannot instantiate type parameter
}
}
List< String> strings = new ArrayList<>();
// if (strings instanceof List< String>) { } // Invalid
if (strings instanceof List) { } // Valid: Raw type check
// List< String>[] array = new List< String>[10]; // Invalid
List>[] array = new List>[10]; // Valid: Unbounded wildcard
class Example {
void print(List< String> list) { }
// void print(List< Integer> list) { } // Invalid: Same erased signature
}