Covariance and contravariance describe how type relationships behave when applied to generic types or arrays.
They answer the question:
If A is a subtype of B, is List<A> also a subtype of List<B>?
Java’s generics are invariant by default, meaning:
List<Integer> is NOT a subtype of List<Number>But using wildcards, we can achieve covariance or contravariance.
Covariance means you can assign a more specific type to a more general reference.
Integer extends NumberBut with generics:
List<Integer> ≠ List<Number> // invarianceTo enable it:
List<? extends Number> list = new ArrayList<Integer>();Here, the list is covariant with respect to Number.
Number n = list.get(0);list.add(10); // ERRORReason: The compiler does not know the exact type → could be List.
Use it when the collection produces values (read-only).
Example:
double sum(List<? extends Number> nums) { ... }Can accept:
- List
- List
- List
Contravariance means you can assign a more general type to a more specific reference.
Example:
List<? super Integer> list = new ArrayList<Number>();Here, the list is contravariant with respect to Integer.
list.add(10); // allowed
list.add(20);Object obj = list.get(0); // returns ObjectBecause actual type could be Integer, Number, or Object.
Without wildcards, Java generics are invariant.
List<Number> nums = new ArrayList<Integer>(); // ❌ Compile errorEven though Integer → Number, the generic types do NOT follow the same hierarchy.
Java arrays behave differently:
Integer[] ia = {1, 2, 3};
Number[] na = ia; // ✔ Allowed (covariant)But this is dangerous:
na[0] = 3.14; // Runtime ArrayStoreExceptionArrays perform runtime type checks Generics rely on compile-time checks and erasure.
PECS stands for:
Producer Extends, Consumer Super
| Wildcard | Meaning | Usage |
|---|---|---|
? extends T |
T or its subclasses | Use when reading |
? super T |
T or its superclasses | Use when writing |
Covariance ( ? extends Number )
Read ✓ | Write ✗
Accepts: List<Integer>, List<Double>, ...
Contravariance ( ? super Integer )
Read ✗ (Object only) | Write ✓
Accepts: List<Integer>, List<Number>, List<Object>
Invariance
List<Number> ≠ List<Integer>
Arrays (unsafe covariance)
Integer[] -> Number[]
List<? extends Number> list = new ArrayList<Integer>();
Number n = list.get(0); // OK
list.add(10); // ERRORList<? super Integer> list = new ArrayList<Object>();
list.add(10); // OK
Integer x = list.get(0); // ERROR (returns Object)List<Number> ln = new ArrayList<Number>();
List<Integer> li = new ArrayList<Integer>();
ln = li; // ERRORdouble sum(List<? extends Number> nums) {
double total = 0;
for (Number n : nums)
total += n.doubleValue();
return total;
}Can pass:
- List
- List
void addIntegers(List<? super Integer> list) {
list.add(1);
list.add(2);
}- Covariance:
? extends T→ allows reading - Contravariance:
? super T→ allows writing
Arrays need runtime checks → unsafe. Generics need compile-time safety → invariant.
No:
List<? extends super Number> // ❌ illegalProducer Extends, Consumer Super.
Because the exact subtype is unknown.
-
Covariance (
? extends T) → read-only, flexible input. -
Contravariance (
? super T) → write-only, flexible output. -
Invariance → generics don't follow subtype relationships.
-
Use PECS: Producer Extends, Consumer Super.
-
Arrays are covariant but unsafe; generics are invariant but safe.