What is the difference between extends T and super T
The fundamental distinction between extends T> and super T> lies in hierarchy traversal direction (covariance vs. contravariance) and the role of the container (data produ...
The fundamental distinction between <? extends T> and <? super T> lies in hierarchy traversal direction (covariance vs. contravariance) and the role of the container (data producer vs. data consumer). Both constructs represent bounded wildcards in Java and are formalized by the PECS guideline (Producer Extends, Consumer Super).
🟢 Junior Level
Core Comparison
| Wildcard | Hierarchy Direction | Role (PECS) | Operations Permitted |
| :— | :— | :— | :— |
| <? extends T> | Type $T$ and any of its subclasses | Producer (source of data) | Read-only as $T$. Adding elements is prohibited (except null). |
| <? super T> | Type $T$ and any of its superclasses | Consumer (destination of data) | Write-only for $T$. Reading elements returns bare Object. |
// 1. <? extends Number> - Reading is SAFE; Writing is FORBIDDEN:
List<? extends Number> producerList = List.of(1, 2.5, 3L);
Number n = producerList.get(0); // Safe read: Guaranteed to be at least a Number
// producerList.add(10); // Compilation error! Writing is prohibited
// 2. <? super Integer> - Writing is SAFE; Reading returns Object:
List<? super Integer> consumerList = new ArrayList<Number>();
consumerList.add(42); // Safe write: Integer can always fit in Number or Object
Object obj = consumerList.get(0); // Read returns Object (specific type unknown)
// Integer val = consumerList.get(0); // Compilation error: Cannot cast implicitly
🟡 Middle Level
1. Why <? extends T> Prohibits Adding Elements
Consider what would happen if the compiler permitted writes into an upper-bounded wildcard:
List<Integer> intList = new ArrayList<>();
List<? extends Number> numbers = intList; // Legal covariance assignment
// Hypothetical compilation if writes were allowed:
// numbers.add(3.14); // Attempting to insert a Double into a List<Integer>!
Integer val = intList.get(0); // Runtime ClassCastException!
List<? extends Number> signifies: “a list of some concrete but unknown subtype of Number“. The actual instance at runtime could be List<Double>, List<Long>, or List<BigDecimal>. To prevent corrupting the underlying collection, the compiler strictly bans adding any reference, allowing only null (since null belongs to all reference types).
2. Why <? super T> Returns Only Object on Read
In List<? super Integer>, the underlying list could be:
List<Integer>List<Number>List<Object>
If the underlying list is List<Number>, it could already contain Double, Float, or BigDecimal instances. Therefore, the compiler cannot guarantee that an extracted item is an Integer or even a Number. The only guaranteed common ancestor of any superclass of Integer is java.lang.Object.
3. The Standard JDK Benchmark: Collections.copy
The JDK standard library’s Collections.copy demonstrates both wildcards working in harmony:
// src produces elements (Producer -> extends)
// dest consumes elements (Consumer -> super)
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
for (int i = 0; i < src.size(); i++) {
dest.set(i, src.get(i)); // Read from src as T, write to dest as T
}
}
This flexibility allows copying a List<Integer> into a List<Number> or List<Object> without type safety violations.
🔴 Senior Level
Type Theory: Invariance, Covariance, and Contravariance
By default, generic collections in Java are strictly invariant: \(\text{Integer} \le \text{Number} \not\Rightarrow \text{List}\langle\text{Integer}\rangle \le \text{List}\langle\text{Number}\rangle\)
Bounded wildcards introduce Use-Site Variance:
- Covariance (
? extends T): Preserves the subtype relationship in the generic container: \(\text{Sub} \le \text{Super} \implies \text{List}\langle\text{Sub}\rangle \le \text{List}\langle?\text{ extends Super}\rangle\) - Contravariance (
? super T): Reverses (inverts) the subtype relationship: \(\text{Sub} \le \text{Super} \implies \text{List}\langle\text{Super}\rangle \le \text{List}\langle?\text{ super Sub}\rangle\)
Wildcard Capture and the Private Helper Pattern
Attempting to read an element from an unbounded or bounded wildcard list and write it back into the same list causes a Wildcard Capture Error:
public void swapFirstTwo(List<?> list) {
// Compilation Error:
// capture of ? cannot be applied to capture of ?
// list.set(0, list.get(0));
}
Why this occurs: The compiler assigns each wildcard a distinct internal type variable (capture#1 of ? for the getter and capture#2 of ? for the setter). Because the compiler cannot prove that capture#1 and capture#2 represent the identical type, it blocks the assignment.
The Solution: Private Helper Method
Capture the unknown wildcard into a named generic type parameter $T$:
public void swapFirstTwo(List<?> list) {
swapHelper(list); // Wildcard is captured into T
}
private <T> void swapHelper(List<T> list) {
list.set(0, list.get(0)); // Compiles cleanly: T matches T exactly!
}
Use-Site Variance (Java) vs. Declaration-Site Variance (Kotlin / C#)
- Declaration-Site Variance (Kotlin / C#): The class designer marks variance at the class or interface declaration (e.g.,
interface Source<out T>in Kotlin orIEnumerable<out T>in C#). All usages ofSource<T>are automatically covariant without needing wildcards at call sites. - Use-Site Variance (Java): Java collections are declared invariants (
List<E>). Variance must be explicitly declared at each method parameter site using wildcards. While more verbose, use-site variance offers greater flexibility: a singleList<E>can serve as a Producer in one method (List<? extends E>) and as a Consumer in another (List<? super E>).
4 Tricky Questions
1. Why can’t you insert an Integer into a List<? extends Number>, even though Integer is a valid subtype of Number?
Answer: Because List<? extends Number> does not mean a list that can hold any Number. It means a list of one specific, unknown subtype of Number.
At runtime, the actual object could be an ArrayList<Double>. If the compiler allowed you to insert an Integer, you would put an integer into a list of doubles, resulting in heap pollution and a delayed ClassCastException when other code reads from ArrayList<Double>. The only literal permitted into ? extends is null.
2. What type does list.get(0) return when list has type List<? super Integer>, and why?
Answer: It returns java.lang.Object.
List<? super Integer> denotes a list parameterized by some unknown supertype of Integer (such as Number or Object). If the underlying collection is actually a List<Number>, it could legitimately contain Float, Double, or Long elements added elsewhere. The compiler cannot guarantee that the extracted element is an Integer or even a Number. The only ancestor guaranteed to cover all possibilities is Object.
3. What is a Wildcard Capture error (e.g., capture#1 of ?), and how do you resolve it?
Answer: A wildcard capture error occurs when code reads an element from a wildcard collection (list.get()) and attempts to write it back into the same collection (list.set()). The compiler creates an anonymous type variable (capture of ?) for each reference to the wildcard. Because it cannot statically prove that the read type and the parameter type are the exact same concrete instantiation, it rejects the operation.
The standard resolution is the Helper Method pattern: create a private static <T> void helper(List<T> list) method. When the public method passes the wildcard list to the helper, the compiler infers $T$ as the concrete captured type, allowing type-safe reads and writes.
4. Can you declare a local variable as List<? super T>, and does it make architectural sense?
Answer: Syntactically, it is completely valid Java:
List<? super Integer> list = new ArrayList<Number>();
list.add(10);
Architecturally, however, declaring local variables with wildcards is considered an anti-pattern. Wildcards are designed for API boundaries (method parameters and return types) to maximize caller flexibility (Use-Site Variance). Within a method body, the developer knows the exact concrete type being instantiated and should use invariant types (List<Number> or List<Integer>) to avoid losing reading or writing capabilities.
🎯 Interview Cheat Sheet
- Core Formula: PECS — Producer Extends, Consumer Super.
<? extends T>(Upper Bound / Covariance):- Use when reading data from a collection.
- Returns
Ton read operations. - Prohibits all write operations (except
null).
<? super T>(Lower Bound / Contravariance):- Use when writing data into a collection.
- Accepts
Tand subtypes ofTon write operations. - Returns only
Objecton read operations.
- Both Read and Write Needed? Do not use wildcards! Use invariant
<T>. - JDK Exemplar:
Collections.copy(List<? super T> dest, List<? extends T> src). - Wildcard Capture: Compiler’s inability to reconcile two
?references. Solved using a private generic helper method<T> void helper(List<T> list). - Return Types: Never return wildcards (
List<? extends T>) from methods; return concrete types (List<T>) to avoid forcing callers to handle wildcards.