Java Generics Wildcards: extends, super and PECS

Introduction An Integer is a Number - but, surprisingly, a List<Integer> is not a List<Number>. This rule keeps generics type-safe, but it would make generic methods very rigid without wildcards. This tutorial explains Java generics wildcards - ?, ? extends and ? super - and the PECS rule for choosing between them. We will start with why generic types are not related the way their type arguments are. We will then use the unbounded wildcard, upper-bounded wildcards for reading, and lower-bounded wildcards for writing. Next we will learn the PECS rule - Producer Extends, Consumer Super - and see it in the Java library. Lastly, we will go through guidelines for using wildcards.

(Note - This tutorial builds on Bounded Type ParametersClick to read tutorial: Bounded Type Parameters in Java Generics.) Why List<Integer> is not a List<Number> If List<Integer> could be used as a List<Number>, this code would be allowed -
Why the compiler forbids it
List<Integer> integers = new ArrayList<>(List.of(1, 2, 3));
List<Number> numbers = integers;      // suppose this were allowed...
numbers.add(3.14);                    // ...a Double is a Number, so this would be fine...
Integer first = integers.get(3);      // ...and now a List<Integer> contains a Double!
So Java does not allow it: generic types are invariant - List<Integer> and List<Number> are unrelated types, even though Integer extends Number. The consequence is that a method taking a List<Number> cannot be called with a List<Integer> -
List cannot be passed as List
//Invariance.java
import java.util.List;

public class Invariance {
  static double sum(List<Number> values) {
    double total = 0;
    for (Number n : values) total += n.doubleValue();
    return total;
  }

  public static void main(String[] args) {
    List<Integer> counts = List.of(3, 7, 5);
    System.out.println(sum(counts));
  }
}
 OUTPUT on compiling the above code
Invariance.java:13: error: incompatible types: List<Integer> cannot be converted to List<Number>
    System.out.println(sum(counts));
                           ^
Note: Some messages have been simplified; recompile with -Xdiags:verbose to get full output
1 error
Note - Arrays work differently: an Integer[] is an Object[]. That is why storing a wrong type in an array is only caught at runtime, with an ArrayStoreException - exactly the kind of runtime failure generics were designed to prevent. The unbounded wildcard - ? A wildcard ? means "some type - I do not know or care which". List<?> is a list of some unknown type, and any list can be passed where a List<?> is expected. In return, you can only read its elements as Object, and cannot add anything except null, because the compiler does not know what type the list really holds.
The unbounded wildcard
//UnboundedWildcard.java
import java.util.List;
import java.util.Set;

public class UnboundedWildcard {
  static void printAll(List<?> items) {
    System.out.print(items.size() + " items:");
    for (Object item : items) {
      System.out.print(" " + item);
    }
    System.out.println();
  }

  static boolean haveCommon(Set<?> a, Set<?> b) {
    for (Object x : a) {
      if (b.contains(x)) return true;
    }
    return false;
  }

  public static void main(String[] args) {
    printAll(List.of("Tom", "Harry"));
    printAll(List.of(3.5, 7.25));
    printAll(List.of('J', 'B'));
    System.out.println("common: " + haveCommon(Set.of(1, 2, 3), Set.of(3, 4)));
  }
}
 OUTPUT of the above code
2 items: Tom Harry
2 items: 3.5 7.25
2 items: J B
common: true
Use ? when the code only uses methods which do not depend on the element type - size(), isEmpty(), contains(), or Object's methods such as toString(). Unlike a raw List, List<?> is still type-safe.
Upper-bounded wildcards - ? extends T List<? extends Number> means "a list of Number or of some subtype of Number". A List<Integer>, a List<Double> and a List<Number> are all accepted.
  • Reading is safe: whatever the real type is, every element is some kind of Number, so elements can be read as Number.
  • Adding is not allowed (except null): the list might be a List<Integer>, so adding a Double - or even an Integer, as the compiler cannot tell which list it is - would be unsafe.
Reading from a ? extends wildcard
//ExtendsWildcard.java
import java.util.List;

public class ExtendsWildcard {
  static double sum(List<? extends Number> values) {
    double total = 0;
    for (Number n : values) {         // reading as Number is safe
      total += n.doubleValue();
    }
    return total;
  }

  public static void main(String[] args) {
    List<Integer> counts = List.of(3, 7, 5);
    List<Double> prices = List.of(19.99, 5.25);
    List<Number> mixed = List.of(1, 2.5, 3L);
    System.out.println("sum(counts) = " + sum(counts));
    System.out.println("sum(prices) = " + sum(prices));
    System.out.println("sum(mixed)  = " + sum(mixed));
  }
}
 OUTPUT of the above code
sum(counts) = 15.0
sum(prices) = 25.24
sum(mixed)  = 6.5
And the compiler stops any attempt to add -
Adding to a ? extends list does not compile
//ExtendsNoAdd.java
import java.util.List;

public class ExtendsNoAdd {
  static void addOne(List<? extends Number> values) {
    values.add(1);
  }
}
 OUTPUT on compiling the above code
ExtendsNoAdd.java:6: error: incompatible types: int cannot be converted to CAP#1
    values.add(1);
               ^
  where CAP#1 is a fresh type-variable:
    CAP#1 extends Number from capture of ? extends Number
Note: Some messages have been simplified; recompile with -Xdiags:verbose to get full output
1 error
The CAP#1 in the message is the compiler's name for "the unknown type this list really holds" - a capture of ? extends Number. Even an int cannot be added, because the list might be a List<Double>. Lower-bounded wildcards - ? super T List<? super Integer> means "a list of Integer or of some supertype of Integer" - a List<Integer>, List<Number> or List<Object>. Here the rules are the other way round -
  • Adding is safe: whichever of those lists it is, an Integer fits in it.
  • Reading gives only Object: the list could be a List<Object> holding anything, so an element can only be read as Object.
Writing to a ? super wildcard
//SuperWildcard.java
import java.util.ArrayList;
import java.util.List;

public class SuperWildcard {
  static void addSquares(List<? super Integer> target, int count) {
    for (int i = 1; i <= count; i++) {
      target.add(i * i);              // adding Integers is safe
    }
  }

  public static void main(String[] args) {
    List<Integer> integers = new ArrayList<>();
    List<Number> numbers = new ArrayList<>(List.of(0.5));
    List<Object> objects = new ArrayList<>(List.of("squares:"));
    addSquares(integers, 3);
    addSquares(numbers, 3);
    addSquares(objects, 3);
    System.out.println("integers = " + integers);
    System.out.println("numbers  = " + numbers);
    System.out.println("objects  = " + objects);
    Object first = objects.get(0);    // reading gives Object
    System.out.println("first element read as Object: " + first);
  }
}
 OUTPUT of the above code
integers = [1, 4, 9]
numbers  = [0.5, 1, 4, 9]
objects  = [squares:, 1, 4, 9]
first element read as Object: squares:
With List<Integer> as the parameter type, only the first call would have compiled. ? super Integer lets the same method fill any list that can hold integers.
The PECS rule - Producer Extends, Consumer Super Which wildcard should a parameter use? Joshua Bloch's rule from Effective Java answers it -
  • Producer Extends: if a parameter produces values your method reads from it, use ? extends T.
  • Consumer Super: if a parameter consumes values your method puts into it, use ? super T.
  • Both: if it does both, use the exact type T, with no wildcard.
A method which copies from one list to another uses both - the source is a producer, and the destination a consumer.
PECS in a copy method
//Pecs.java
import java.util.ArrayList;
import java.util.List;

public class Pecs {
  static <T> void copy(List<? extends T> source, List<? super T> destination) {
    for (T item : source) {           // source produces Ts
      destination.add(item);          // destination consumes Ts
    }
  }

  public static void main(String[] args) {
    List<Integer> scores = List.of(72, 85, 90);
    List<Double> prices = List.of(9.99, 4.5);
    List<Number> allNumbers = new ArrayList<>();
    List<Object> everything = new ArrayList<>();

    copy(scores, allNumbers);         // T = Integer: Integer list -> Number list
    copy(prices, allNumbers);         // T = Double : Double list  -> Number list
    copy(allNumbers, everything);     // T = Number : Number list  -> Object list
    System.out.println("allNumbers = " + allNumbers);
    System.out.println("everything = " + everything);
  }
}
 OUTPUT of the above code
allNumbers = [72, 85, 90, 9.99, 4.5]
everything = [72, 85, 90, 9.99, 4.5]
Without wildcards - copy(List<T> source, List<T> destination) - none of these calls would compile, because the two lists have different element types. This is exactly how the JDK's own Collections.copy() is declared.
PECS in the Java library Once you know PECS, you see it everywhere in the JDK -
  • Collection.addAll(Collection<? extends E> c) - the argument produces elements to add.
  • List.sort(Comparator<? super E> c) - the comparator consumes elements to compare them, so a Comparator<Object> or Comparator<Number> can sort a List<Integer>.
  • Collections.max(Collection<? extends T> coll) with T extends Comparable<? super T> - the collection produces, and the comparison consumes.
  • Stream.map(Function<? super T, ? extends R> mapper) - the function consumes Ts and produces Rs.
A Comparator sorting a List, thanks to ? super
//LibraryPecs.java
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

public class LibraryPecs {
  public static void main(String[] args) {
    Comparator<Object> byText = Comparator.comparing(Object::toString);
    List<String> names = new ArrayList<>(List.of("Tom", "Ethan", "Harry"));
    names.sort(byText);                      // sort(Comparator<? super String>)
    List<Integer> numbers = new ArrayList<>(List.of(30, 4, 200));
    numbers.sort(byText);                    // the same comparator sorts Integers - as text
    System.out.println(names + " " + numbers);

    List<Number> collected = new ArrayList<>();
    collected.addAll(List.of(1, 2));         // addAll(Collection<? extends Number>)
    collected.addAll(List.of(3.5));
    System.out.println(collected);
  }
}
 OUTPUT of the above code
[Ethan, Harry, Tom] [200, 30, 4]
[1, 2, 3.5]
One Comparator<Object> sorted both a list of strings and a list of integers - by their text, so 200 comes before 30. More about comparators in The Complete Java 8 Comparator TutorialClick to read The Complete Java 8 Comparator Tutorial. Guidelines for using wildcards
  • Use wildcards in method parameters to make APIs flexible, following PECS.
  • Do not use wildcards in return types. A method returning List<? extends Number> forces every caller to deal with the wildcard. Return a concrete type such as List<Number>.
  • Use a named type parameter when the same type must appear more than once - see bounded type parameters vs wildcardsClick to read tutorial: Bounded Type Parameters in Java Generics.
  • Prefer List<?> to a raw List when you truly do not care about the element type - it stays type-safe.
Summary In this tutorial we first saw that generic types are invariant - a List<Integer> is not a List<Number> - and why that keeps them type-safe. We then used the unbounded wildcard ? for code which does not care about the element type. Next we used ? extends T to read from a list of any subtype, and ? super T to write into a list of any supertype. We then learned the PECS rule - Producer Extends, Consumer Super - and applied it to a copy method and found it throughout the JDK. Lastly, we went through guidelines for using wildcards. In the next tutorial, we will see what happens to all these types at runtime with type erasureClick to read tutorial: Type Erasure in Java Generics and Its Limitations.