
| 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
}