Generics — Type Parameters, Wildcards, and Type Erasure
1171 words
6 min read
Visual companion
Python
Type and operator map
Python Week 1: the first filter for runtime behavior
View
Revision summary
What this note is really saying
Short form
# Generics — Type Parameters, Wildcards, and Type Erasure ## 🎯 Learning Objectives - Define generic classes, interfaces, and methods - Use bounded type parameters (`extends`, `super`) - Understand wildcards (`?`, `? extends T`, `?

Generics — Type Parameters, Wildcards, and Type Erasure
🎯 Learning Objectives
- Define generic classes, interfaces, and methods
- Use bounded type parameters (
extends,super) - Understand wildcards (
?,? extends T,? super T) - Explain type erasure and its implications
- Compare generics with Python's dynamic typing
1. Why Generics?
1.1 The Problem (Before Generics)
javaList list = new ArrayList(); list.add("Hello"); list.add(42); // Accidentally added Integer — no compile error String s = (String) list.get(0); // OK String t = (String) list.get(1); // ClassCastException at runtime!
Without generics, collections hold
Object, and you must cast — the compiler can't check types.1.2 The Solution (With Generics)
javaList<String> list = new ArrayList<>(); list.add("Hello"); list.add(42); // Compile error! Can't add Integer to List<String> String s = list.get(0); // No cast needed — compiler knows it's String
Generics provide type safety at compile time, eliminating the need for casts.
2. Generic Classes
javapublic class Box<T> { // T is a type parameter private T content; public void set(T content) { this.content = content; } public T get() { return content; } } // Usage: Box<String> stringBox = new Box<>(); stringBox.set("Hello"); String s = stringBox.get(); // No cast Box<Integer> intBox = new Box<>(); intBox.set(42); int i = intBox.get();
Naming conventions:
E— Element (collections)K,V— Key, Value (maps)T— TypeR— Return type
3. Generic Methods
javapublic class Utils { // Generic method — type parameter before return type public static <T> T getMiddle(T... array) { return array[array.length / 2]; } } // Type inference: String mid = Utils.getMiddle("A", "B", "C"); // String Integer mid2 = Utils.getMiddle(1, 2, 3, 4); // Integer
4. Bounded Type Parameters
Restrict a type parameter to a specific hierarchy:
java// T must be a Number or subclass public static <T extends Number> double sum(T a, T b) { return a.doubleValue() + b.doubleValue(); } sum(5, 10); // OK (Integer) sum(3.14, 2.71); // OK (Double) // sum("Hello", "World"); // Compile error!
Multiple bounds:
javapublic class Bound<T extends Comparable<T> & Serializable> { // T must implement BOTH Comparable AND Serializable }
5. Wildcards (?)
5.1 Unbounded Wildcard ?
javapublic void printList(List<?> list) { // Accept any type for (Object o : list) { System.out.println(o); } // list.add("X"); // ERROR! Can't add (type unknown) }
5.2 Upper-Bounded Wildcard ? extends T
Read access allowed. Write not allowed (except null).
javapublic double sumOfNumbers(List<? extends Number> list) { double sum = 0; for (Number n : list) { sum += n.doubleValue(); } return sum; } sumOfNumbers(Arrays.asList(1, 2, 3)); // List<Integer> sumOfNumbers(Arrays.asList(1.5, 2.5, 3.5)); // List<Double>
5.3 Lower-Bounded Wildcard ? super T
Write access allowed. Read gives
Object.javapublic void addNumbers(List<? super Integer> list) { list.add(1); // OK list.add(2); // OK // Integer i = list.get(0); // ERROR! Type is ? super Integer Object o = list.get(0); // OK }
5.4 PECS Rule (Producer Extends, Consumer Super)
? extends T— producer: you readTfrom it? super T— consumer: you writeTto it
java// Copy from src (producer) to dest (consumer) public static <T> void copy(List<? extends T> src, List<? super T> dest) { for (T item : src) dest.add(item); }
6. Type Erasure
At runtime, generic type information is erased. The compiler uses it for checking, then removes it.
java// Source code: List<String> strings = new ArrayList<>(); List<Integer> integers = new ArrayList<>(); // After erasure (at runtime): List strings = new ArrayList(); // Both look identical List integers = new ArrayList(); System.out.println(strings.getClass() == integers.getClass()); // true
Implications of erasure:
- Cannot use
new T()(nonewwith type parameter) - Cannot use
instanceof T(no runtime type check) - Cannot create generic arrays:
new T[10] - Static fields are shared across all parameterizations
javapublic class MyClass<T> { // static T field; // ERROR! T is erased // T[] array = new T[10]; // ERROR! Can't create generic array // if (obj instanceof T) { } // ERROR! T erased }
7. Java vs Python: Generics
| Feature | Java | Python (3.5+ typing) |
|---|---|---|
| Runtime | Erased (compile-time only) | Present at runtime |
| Enforcement | Enforced at compile time | Type hints only (not enforced) |
| Syntax | `` | Generic[T] |
| Bounds | T extends Number | N/A |
| Wildcards | ? extends T, ? super T | N/A |
| Performance | No overhead (erased) | Some overhead |
| Variance | Declaration-site + use-site | N/A |
8. Practice Questions
Q1: Why can't you createnew T()in a generic method?Answer: Due to type erasure,Tis erased toObjectat runtime.new T()would becomenew Object(), which likely isn't what you want. The compiler prevents this. Q2: What is the difference betweenListandList?Answer:List<Object>can only holdObjectexplicitly.List<?>accepts any parameterizedList(List<String>, List<Integer>, etc.). You cannot add elements (except null) toList<?>because the type is unknown. Q3: What is the output?javaList<Integer> ints = new ArrayList<>(); List<? extends Number> nums = ints; // OK: Integer extends Number Integer i = 42; // nums.add(i); // ERROR! Can't add to ? extendsAnswer: The code compiles but the add is commented out.? extendsis a producer — you can read from it but not write to it (except null). Q4: What does PECS stand for?Answer: Producer Extends, Consumer Super. Use? extends Twhen you produce/read values. Use? super Twhen you consume/write values. Q5: Can you passListto a method expectingList?Answer: No! This is a common misconception.List<Integer>is NOT a subtype ofList<Object>. Generics are invariant by default. You needList<? extends Object>(which is justList<?>). Q6: Write a genericmaxmethod.javapublic static <T extends Comparable<T>> T max(T a, T b) { return (a.compareTo(b) >= 0) ? a : b; }Q7: Why do we need wildcards when we have bounded type parameters?Answer: Bounded type parameters work at the method level (<T extends Number>). Wildcards work at the type usage level (List<? extends Number>). They enable variance in generic types, which is crucial for collections. Q8: What is the bridge method in the context of generics?Answer: When a subclass overrides a generic method, the compiler sometimes generates a bridge method with erased types, to maintain polymorphism at the bytecode level. For example, ifclass MyComparator implements Comparator<String>, the compiler generatescompare(Object, Object)that delegates tocompare(String, String).
📐 Key Concepts
| Feature | Syntax | Purpose |
|---|---|---|
| Generic class | class Box | Type-safe container |
| Generic method | T method(T arg) | Type-safe method |
| Bounded type | `` | Restrict type hierarchy |
| Unbounded wildcard | List | Accept any type |
| Upper bounded | List | Read T values |
| Lower bounded | List | Write T values |
🔗 Cross-References
- Next: Collections Framework Join Discord Previous5.2 Iterators & CallbacksNext6.2 Collections Framework