Generics Part- 2
10. Inheritance and Subtyping with Generics
10.1. Do Generics Follow Inheritance?
This is a common confusion among Java developers.
Let’s start with a basic inheritance fact:
Integer is a subtype of Number
But this does NOT mean:
List is a subtype of List
In Java, generic types are invariant. That means:
- List and List are completely unrelated types, even though Integer is a subtype of Number.
10.2. Invariance in Java Generics
What Is Invariance?
Invariance means that:
- Box is not a subtype of Box, nor vice versa, even though Child is a subtype of Parent.
This is done to preserve type safety.
Example: Compilation Error
import java.util.*;
public class InvarianceDemo {
public static void addNumbers(List numbers) {
numbers.add(1);
numbers.add(2.5);
}
public static void main(String[] args) {
List intList = new ArrayList<>();
// addNumbers(intList); // Compile-time error
}
}
Even though Integer is a subtype of Number, you cannot pass List to a method expecting List.
10.3. How to Handle This? Use Wildcards
To make your method flexible, you can use wildcards ( or ).
Example: Use of
public static void printNumbers(List numbers) {
for (Number num : numbers) {
System.out.println(num);
}
}
Now you can pass:
- List
- List
- List etc.
Usage:
public static void main(String[] args) {
List intList = Arrays.asList(1, 2, 3);
List doubleList = Arrays.asList(1.1, 2.2, 3.3);
printNumbers(intList); // OK
printNumbers(doubleList); // OK
}
10.4. Covariance and Contravariance in Java
These terms come from type theory. Here's what they mean in Java Generics:
Covariance:
- Used when the method only reads from the collection.
- You cannot write to it.
List list = new ArrayList();
Number n = list.get(0); // OK
// list.add(10); // Error
Contravariance:
- Used when the method writes into the collection.
- You cannot safely read specific types from it.
List list = new ArrayList();
list.add(10); // OK
// Integer n = list.get(0); // Error: type is Object
Invariant: List
- Exact type only.
- List can only accept List.
10.5. Generics and Class Hierarchies
Let’s say you have:
class Animal { }
class Dog extends Animal { }
class Cat extends Animal { }
Generic Class Example
class Cage {
T animal;
public Cage(T animal) {
this.animal = animal;
}
public T getAnimal() {
return animal;
}
}
Cage dogCage = new Cage<>(new Dog());
Cage catCage = new Cage<>(new Cat());
// Cage animalCage = dogCage; // ❌ Compile-time error
Cage is not a subtype of Cage, even though Dog is a subtype of Animal.
10.6. Arrays vs Generics (Important Distinction)
In Java:
Arrays are covariant:
Integer[] intArr = new Integer[5]; Number[] numArr = intArr; // Allowed (but unsafe)
Generics are invariant:
List intList = new ArrayList<>(); List numList = intList; // Not allowed
This is why arrays can throw ArrayStoreException, while generics ensure type safety at compile-time.
10.7. Wildcards vs Subtyping
Statement | Allowed? | Explanation |
---|---|---|
List list = new ArrayList(); | ❌ | Invariant |
List list = new ArrayList(); | ✅ | Covariant |
List list = new ArrayList(); | ✅ | Contravariant |
10.8. Practical Use Case: Copying from One List to Another
public class CopyExample {
public static void copy(List dest, List src) {
for (T item : src) {
dest.add(item);
}
}
public static void main(String[] args) {
List numbers = new ArrayList<>();
List integers = Arrays.asList(1, 2, 3);
copy(numbers, integers);
System.out.println(numbers);
}
}
Output:
[1, 2, 3]
This uses both (for writing) and (for reading) — a practical application of PECS.
Absolutely. Below is the continuation from Chapter 11 to 15 of your Java Generics educational series, followed by a complete summary of all chapters.
11. Limitations of Generics in Java
Generics provide strong benefits, but they come with certain limitations due to type erasure and how Java was designed for backward compatibility.
11.1 Cannot Use Primitive Types Directly
List list = new ArrayList<>(); // ❌ Compile-time error
You must use wrapper classes like Integer, Double, etc.
11.2 Cannot Instantiate Generic Type
class Box {
T value = new T(); // ❌ Compile-time error
}
Java doesn't know what constructor to call due to type erasure.
11.3 Cannot Create Generic Arrays
T[] arr = new T[10]; // ❌ Compile-time error
List[] listArray = new ArrayList[10]; // ❌
Use Object[] with casting (unsafe) or List.
11.4 Cannot Use instanceof with Parameterized Type
if (obj instanceof List) { } // ❌ Compile-time error
Use raw types or bounded wildcards:
if (obj instanceof List) { }
11.5 Cannot Use Generics in Static Context
class MyClass {
static T value; // ❌ Compile-time error
}
Because static members belong to the class, not to any specific type.
12. Generic vs Object
Without Generics (Using Object)
List list = new ArrayList();
list.add("Hello");
String s = (String) list.get(0); // Casting required
With Generics
List list = new ArrayList<>();
list.add("Hello");
String s = list.get(0); // No casting
Advantages of Generics over Object
Feature | Generics | Object |
---|---|---|
Type Safety | ✅ Yes | ❌ No |
Compile-time Check | ✅ Yes | ❌ No |
Type Casting | ❌ No | ✅ Yes |
Reusability | ✅ High | ✅ High |
Runtime Errors | ❌ Less | ✅ More likely |
13. Custom Generic Data Structures
Let’s create a simple Generic Stack.
class Stack {
private List elements = new ArrayList<>();
public void push(T value) {
elements.add(value);
}
public T pop() {
if (elements.isEmpty()) throw new EmptyStackException();
return elements.remove(elements.size() - 1);
}
public boolean isEmpty() {
return elements.isEmpty();
}
}
Usage:
public class StackExample {
public static void main(String[] args) {
Stack intStack = new Stack<>();
intStack.push(10);
intStack.push(20);
System.out.println(intStack.pop()); // 20
}
}
14. Advanced Examples and Case Studies
Type-Safe API
class Response {
private T data;
public Response(T data) { this.data = data; }
public T getData() { return data; }
}
Use for designing return types from APIs or services.
Reusable Utilities with Generics
public class Utils {
public static boolean isEqual(T a, T b) {
return a.equals(b);
}
}
Case Study: Sorting Using Comparator
List names = Arrays.asList("Zara", "Mohan", "Aman");
Collections.sort(names, new Comparator() {
public int compare(String s1, String s2) {
return s1.compareTo(s2);
}
});
✅ Conceptual Questions
1. What is type erasure in Java?
Answer:
Type erasure is the process by which the Java compiler removes all generic type information during compilation and replaces them with their raw types (e.g., List<T> becomes List).
This ensures backward compatibility with older Java versions that did not support generics.
2. Difference between List, List<?>, and List<Object>?
Type | Description |
---|---|
List | Raw type – can hold any type of object, but using it loses type safety. Avoid using raw types. |
List<?> | List of unknown type – you can read elements as Object but cannot add anything except null. |
List<Object> | A list that can hold any type of object, but you can only pass another List<Object> (not List<String>). |
✅ Note:
List<?> ≠ List<Object> in practice — because generics are invariant.
3. Can we create an array of generics? Why not?
Answer:
No, you cannot directly create arrays of generic types (e.g., new List<String>[10]).
Because:
- Arrays are reifiable (their runtime type is known).
- Generics are non-reifiable (type info erased at runtime).
→ Mixing them leads to heap pollution and type safety issues.
4. What are the benefits of using generics?
✅ Benefits:
- Type safety (detects type errors at compile time).
- Eliminates need for explicit casting.
- Code reusability and cleaner code.
- Improves readability and maintainability.
5. Can we use primitives with generics?
Answer:
No. Generics only work with objects, not primitives.
But Java provides autoboxing (e.g., int → Integer, double → Double) so you can use wrapper classes in generics.
✅ Sample MCQs
Q1. Which of the following declarations is invalid?
A. List<Integer> list = new ArrayList<>();
B. List<int> list = new ArrayList<>();
C. List<?> list = new ArrayList<>();
👉 Answer: B
✅ Because generics do not support primitive types like int.
Q2. What does ? super T mean?
A. Type T or its superclass
B. Type T or its subclass
C. Only type T
👉 Answer: A
✅ Explanation:
- ? super T → Lower bounded wildcard (T or any superclass of T).
- ? extends T → Upper bounded wildcard (T or any subclass of T).
Q3. Which of these statements is true about generics in Java?
A. Generics are implemented using type erasure
B. Generics increase runtime overhead
C. Generics allow primitive types directly
D. Generics were introduced in Java 1.2
👉 Answer: A
✅ Because generics are compile-time only and implemented through type erasure (introduced in Java 5).
Key Takeaways on Java Generics
1. Inheritance & Subtyping
- Integer is a subtype of Number.
- But List<Integer> is not a subtype of List<Number> → Generics are invariant.
2. Invariance
- Box<Child> ≠ Box<Parent> even if Child extends Parent.
- Ensures compile-time type safety.
3. Wildcards Usage (? extends, ? super)
- ? extends T → Covariant → Producer, read-only (safe to read).
- ? super T → Contravariant → Consumer, write-only (safe to add).
🔹 PECS Rule:
Producer Extends, Consumer Super
4. Covariance vs Contravariance
- Arrays → Covariant → Unsafe (ArrayStoreException).
- Generics → Invariant → Safer (checked at compile time).
5. Wildcards vs Subtyping
- List<Integer> ≠ List<Number>.
- But List<? extends Number> can accept List<Integer>, List<Float>, etc.
6. Generic Class Hierarchies
- Cage<Integer> ≠ Cage<Number>
→ Each parameterized type is a distinct type.
7. Limitations of Generics
❌ Cannot use primitives (List<int> → invalid).
❌ Cannot instantiate type parameter (new T() → invalid).
❌ Cannot create generic arrays (new List<String>[10] → invalid).
❌ Cannot use instanceof with parameterized types (obj instanceof List<String> → invalid).
❌ Cannot use generic types in static context (type parameter not available).
8. Generics vs Object
- Without generics → need explicit casting, risk of ClassCastException.
- With generics → compile-time type safety, no casting needed.
9. Custom Data Structures
Generics enable reusable, type-safe structures like Stack<T>, Queue<T>, or Response<T>.
10. Practical Utilities
Generic methods:
public static <T> boolean isEqual(T a, T b) { return a.equals(b); }
- Copying collections safely:
Use both ? extends (producer) and ? super (consumer) wildcards.
11. Core Rule
Concept | Description |
---|---|
Arrays | Covariant (checked at runtime → unsafe) |
Generics | Invariant (checked at compile-time → safer) |
✅ Final Check Summary
Section | Status |
---|---|
Type Erasure | ✅ Correct |
Difference between Lists | ✅ Correct, reformatted |
Array of Generics | ✅ Correct |
Benefits of Generics | ✅ Correct |
Primitives with Generics | ✅ Correct |
MCQs | ✅ Fixed syntax & confirmed correctness |
Takeaways | ✅ All accurate and conceptually sound |
Missing Questions | ❌ None missing |