Question 16 Β· Section 20

What is the difference between and ?

Upper bound: type T or any of its subtypes.

Language versions: English Russian Ukrainian

🟒 Junior Level

<? extends T> β€” covariance (Producer).

  • You can read elements as type T.
  • Cannot write (except null).
List<? extends Number> numbers = List.of(1, 2, 3);
Number n = numbers.get(0);  // βœ… can read
// numbers.add(4);  // ❌ cannot write

<? super T> β€” contravariance (Consumer).

  • You can write elements of type T.
  • Can only read as Object.
List<? super Integer> integers = new ArrayList<>();
integers.add(42);  // βœ… can write
// Integer i = integers.get(0);  // ❌ can only read as Object

Simple rule (PECS):

  • Producer Extends β€” reading data β†’ ? extends T
  • Consumer Super β€” writing data β†’ ? super T

🟑 Middle Level

Covariance β€” <? extends T>

Upper bound: type T or any of its subtypes.

List<? extends Number> nums = new ArrayList<Integer>();
Number n = nums.get(0);  // βœ… Integer is a Number
// nums.add(42);  // ❌ compiler doesn't know if it's Integer or Double

Why you cannot write: List<? extends Number> could be a List<Integer> or List<Double>. If writing Double were allowed but the actual list is List<Integer> β€” integrity is broken.

Contravariance β€” <? super T>

Lower bound: type T or any of its supertypes.

List<? super Integer> target = new ArrayList<Number>();
target.add(42);  // βœ… Integer fits into Number or Object
Object o = target.get(0);  // βœ… can only read as Object

Why you cannot read typed values: List<? super Integer> could be List<Number> or List<Object>. There is no guarantee that Integer elements are stored there.

Comparison

Characteristic <? extends T> <? super T>
Role Producer (source) Consumer (sink)
Read βœ… As T ❌ Only as Object
Write ❌ Forbidden βœ… As T
Hierarchy T and all below T and all above

Capture of Wildcard

Common mistake:

void reverse(List<?> list) {
    // list.set(0, list.get(0)); // ERROR: capture of ?
}

The compiler cannot match the type you β€œgot” with the type for writing. Solution: A helper generic method that β€œcaptures” the type:

void reverse(List<?> list) { reverseHelper(list); }
private <T> void reverseHelper(List<T> list) {
    // now T is known
}

πŸ”΄ Senior Level

Use-site variance

Java uses variance at the use site β€” in method parameters. This differs from Kotlin/Scala, which have declaration-site variance (out T / in T).

Wildcards do NOT prevent allocations

Using wildcards in method signatures does not prevent allocations. ArrayList<Integer> allocates Integer objects regardless of whether it is passed as List<? extends Number>. Wildcards are only compile-time type checking.

Unbounded Wildcard <?>

This is effectively <? extends Object>. Useful when the code only uses Object methods:

void printSize(List<?> list) {
    System.out.println(list.size());  // only Object methods
}

Best Practices

// βœ… extends β€” for extracting data (Producer)
public double sum(List<? extends Number> numbers) { ... }

// βœ… super β€” for accumulating data (Consumer)
public void fill(List<? super Integer> list, int value) { ... }

// βœ… No wildcard when you need both read and write
public <T> void swap(List<T> list, int i, int j) { ... }

// ❌ Extends for writing
// ❌ Super for reading a specific type

🎯 Interview Cheat Sheet

Must know:

  • <? extends T> β€” covariance (Producer): can read as T, cannot write (except null)
  • <? super T> β€” contravariance (Consumer): can write T, can only read as Object
  • List<String> does NOT inherit from List<Object> β€” generics are invariant
  • PECS rule: Producer Extends, Consumer Super
  • Wildcards β€” use-site variance (unlike Kotlin’s declaration-site variance)
  • <?> = <? extends Object> β€” for cases when only Object methods are needed

Frequent follow-up questions:

  • Why does <? extends T> forbid writing? β€” The compiler doesn’t know the actual type (could be List), writing would break integrity
  • What can you write into List<? super Integer>? β€” Integer and its subclasses, because they fit into any supertype
  • What is capture of wildcard? β€” The compiler cannot link get() with set() for ?, a generic helper method is needed
  • When is wildcard NOT needed? β€” When you need both read and write β€” use <T>

Red flags (DO NOT say):

  • ❌ β€œList<? extends Number> could be List" β€” Yes, but you cannot add Integer to it
  • ❌ β€œ<? super T allows reading as T” β€” You can only read as Object
  • ❌ β€œWildcards prevent allocations” β€” This is only a compile-time check
  • ❌ β€œList<?> is a read-only list” β€” The compiler blocks writes due to unknown type, not immutable

Related topics:

  • [[11. What are Generics in Java]]
  • [[13. What is type erasure]]
  • [[17. What is PECS (Producer Extends Consumer Super)]]
  • [[21. What is the difference between List<?> and List]]
  • [[24. How do Generics work with inheritance]]