Java Generics Wildcards: extends, super and PECS
Introduction
An
(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
So Java does not allow it: generic types are invariant -
OUTPUT on compiling the above code
Note - Arrays work differently: an
OUTPUT of the above code
Use
Upper-bounded wildcards - ? extends T
OUTPUT of the above code
And the compiler stops any attempt to add -
OUTPUT on compiling the above code
The
OUTPUT of the above code
With
The PECS rule - Producer Extends, Consumer Super
Which wildcard should a parameter use? Joshua Bloch's rule from Effective Java answers it -
OUTPUT of the above code
Without wildcards -
PECS in the Java library
Once you know PECS, you see it everywhere in the JDK -
OUTPUT of the above code
One
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!
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));
}
}
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
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)));
}
}
2 items: Tom Harry 2 items: 3.5 7.25 2 items: J B common: true
? 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.
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 asNumber. - Adding is not allowed (except
null): the list might be aList<Integer>, so adding aDouble- or even anInteger, 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));
}
}
sum(counts) = 15.0 sum(prices) = 25.24 sum(mixed) = 6.5
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);
}
}
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
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
Integerfits in it. - Reading gives only Object: the list could be a
List<Object>holding anything, so an element can only be read asObject.
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);
}
}
integers = [1, 4, 9] numbers = [0.5, 1, 4, 9] objects = [squares:, 1, 4, 9] first element read as Object: squares:
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.
- 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.
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);
}
}
allNumbers = [72, 85, 90, 9.99, 4.5] everything = [72, 85, 90, 9.99, 4.5]
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.
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 aComparator<Object>orComparator<Number>can sort aList<Integer>.Collections.max(Collection<? extends T> coll)withT extends Comparable<? super T>- the collection produces, and the comparison consumes.Stream.map(Function<? super T, ? extends R> mapper)- the function consumesTs and producesRs.
A Comparator
//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);
}
}
[Ethan, Harry, Tom] [200, 30, 4] [1, 2, 3.5]
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 asList<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.
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.Core Java Tutorials
Bounded Type Parameters in Java GenericsClick to read tutorial: Bounded Type Parameters in Java Generics Type Erasure in Java Generics and Its LimitationsClick to read tutorial: Type Erasure in Java Generics and Its Limitations Introduction to Java Generics: Why and HowClick to read tutorial: Introduction to Java Generics: Why and How Java Generic Classes, Interfaces and MethodsClick to read tutorial: Java Generic Classes, Interfaces and Methods Java Math Class: Essential Methods with ExamplesClick to read tutorial: Java Math Class: Essential Methods with Examples
Bounded Type Parameters in Java GenericsClick to read tutorial: Bounded Type Parameters in Java Generics Type Erasure in Java Generics and Its LimitationsClick to read tutorial: Type Erasure in Java Generics and Its Limitations Introduction to Java Generics: Why and HowClick to read tutorial: Introduction to Java Generics: Why and How Java Generic Classes, Interfaces and MethodsClick to read tutorial: Java Generic Classes, Interfaces and Methods Java Math Class: Essential Methods with ExamplesClick to read tutorial: Java Math Class: Essential Methods with Examples