Generics

Wildcards: ? extends (Upper Bounds)

15 min Lesson 5 of 13

Wildcards: ? extends (Upper Bounds)

You now know how to write generic classes and methods. But sometimes you do not need to produce a specific type — you only need to read from a generic collection and you want to accept many related types at once. Upper-bounded wildcards give you exactly that flexibility.

The Problem: Generic Types Are Invariant

One of the most surprising things in Java generics is that List<Double> is not a subtype of List<Number>, even though Double is a subtype of Number. This property is called invariance.

Why? If List<Double> were a List<Number>, you could write an Integer into it — breaking type safety. Java prevents this at compile time.

List<Double> doubles = List.of(1.5, 2.5); // List<Number> numbers = doubles; // COMPILE ERROR — invariant!

So how do you write a helper method that can sum any list of numbers — integers, doubles, floats?

Upper-Bounded Wildcards: ? extends T

The wildcard ? means "some unknown type". Adding extends T constrains that unknown type to be T or any subclass of T. The result — ? extends T — is called an upper-bounded wildcard.

public static double sum(List<? extends Number> list) { double total = 0; for (Number n : list) { total += n.doubleValue(); } return total; }

Now you can call sum with a List<Integer>, a List<Double>, or even a List<BigDecimal>:

List<Integer> ints = List.of(1, 2, 3); List<Double> doubles = List.of(1.1, 2.2); List<Float> floats = List.of(0.5f, 1.5f); System.out.println(sum(ints)); // 6.0 System.out.println(sum(doubles)); // 3.3 System.out.println(sum(floats)); // 2.0
Covariance: List<? extends Number> is covariant — it accepts List<Number>, List<Integer>, List<Double>, and any other List of a Number subtype. This mirrors the covariance of plain arrays (Integer[] is an Object[]), but at the generic level.

The Producer Role: Read, Do Not Write

There is a critical restriction: you cannot add elements to a List<? extends T>. The compiler does not know the exact type — it could be a List<Integer>, a List<Double>, or anything else. Writing an Integer into what might be a List<Double> would be unsound.

public static void tryAdd(List<? extends Number> list) { // list.add(42); // COMPILE ERROR — cannot add // list.add(null); // also forbidden (except the null literal in older Java) Number n = list.get(0); // OK — reading is always safe }

This is summarised by the Producer Extends half of the PECS mnemonic (Producer Extends, Consumer Super). A ? extends T list produces values of type T that you can read; it does not consume anything.

PECS rule of thumb: If you only read from a collection, use ? extends T. If you only write to it, use ? super T (covered in the next lesson). If you do both, use a concrete type parameter.

Practical Example: Copying a List

The standard library method Collections.copy(dest, src) demonstrates PECS elegantly. Here is a simplified version:

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)); } }

Focus on src: it is List<? extends T> — we only read from it, so it is a producer. The dest is ? super T — we only write to it, so it is a consumer. You can now copy a List<Integer> into a List<Number>:

List<Number> dest = new ArrayList<>(List.of(0.0, 0.0)); List<Integer> src = List.of(1, 2); copy(dest, src); System.out.println(dest); // [1, 2]

Upper Bounds Beyond Collections

Upper-bounded wildcards are not limited to List. They work with any generic type. Here is a method that compares two boxes where the contained type is Comparable:

public static <T extends Comparable<T>> T max(List<? extends T> list) { if (list.isEmpty()) throw new IllegalArgumentException("Empty list"); T result = list.get(0); for (T item : list) { if (item.compareTo(result) > 0) result = item; } return result; } System.out.println(max(List.of(3, 1, 4, 1, 5))); // 5 System.out.println(max(List.of("banana", "apple", "cherry"))); // cherry
Common confusion: List<? extends Number> and List<T extends Number> look similar but behave differently. The wildcard version is used at the call site — it accepts many specific types. The bounded type parameter T is used when you need to name the type and reuse it (e.g., return it). Choose the type parameter when the type identity matters; choose the wildcard when it does not.

Summary

  • Generic types are invariant: List<Double> is not a List<Number>.
  • ? extends T makes a type covariant — it accepts T and all subtypes.
  • You can read from a ? extends T collection but never write to it.
  • This is the Producer Extends part of PECS.
  • It is ideal for helper methods that process a collection without modifying it.