Wildcards: reading the JDK's signatures
On This Lesson 11 topics
Lesson Overview
Open the documentation of almost any collection method and you will find a question mark inside the angle brackets: Collection<? extends E>, List<? super T>, Collection<?>. These are wildcards. They look like noise to a beginner, but each carries a precise promise about what the method will do with your collection.
This lesson is a reading lesson. You will not write many wildcards, but you must be able to read them, because every JDK and library signature uses them. The crate picture helps once more: a List<? extends Number> is a crate whose label you cannot read in full, but you know it says "number things". You may take items out and treat them as numbers; you may not put something in, because you do not know which exact kind of number crate it is.
Why this matters: when a method refuses your argument, the signature is the explanation. Reading wildcards turns the error from a mystery into a lookup.
This lesson builds on "Generic classes", "Generic methods", and "Bounded types".
Learning Objectives
By the end of this lesson you will be able to:
- Explain why
List<Integer>is not aList<Number>. - Read
?,? extends T, and? super Tas "unknown type", "unknown subtype of T", and "unknown supertype of T". - Write a method that accepts
List<Integer>andList<Double>usingList<? extends Number>. - Explain why you can read from, but not add to, a
List<? extends Number>. - Use
? super Integerfor a list you only write into. - Apply the rule "producer extends, consumer super" to read real JDK signatures.
Written Explanation
The gap that wildcards fill
You met this in lesson 2: List<Integer> is not a List<Number>. Suppose we want a method that sums numbers:
static double sum(List<Number> nums) { ... }Then sum(List.of(1, 2, 3)) is rejected, because List.of(1, 2, 3) is a List<Integer>:
error: incompatible types: List<Integer> cannot be converted to List<Number>The reason is safety. If a List<Integer> could be used as a List<Number>, then the method could add a Double to it, and your list of integers would contain a double. The Oracle tutorial puts it this way: generic types are not related merely because their type arguments are.
We could write one sum per type, but that is what generics are meant to avoid. A wildcard states what the method really needs.
? extends T: a producer you read from
static double sum(List<? extends Number> nums) {
double total = 0;
for (Number n : nums) {
total += n.doubleValue();
}
return total;
}List<? extends Number> means "a list of some type that is Number or a subtype, and I do not know which". Now List<Integer>, List<Double>, and List<Number> are all accepted:
sum(List.of(1, 2, 3)); // 6.0
sum(List.of(1.5, 2.5)); // 4.0Inside the method, whatever comes out of the list is at least a Number, so reading is safe. Oracle's wording is that an upper bounded wildcard relaxes the restriction so a method can accept a type argument that is a given type or any subtype.
But you cannot put anything in. The list might really be a List<Integer>, or a List<Double>; the compiler cannot know which kind of number is safe to add:
List<? extends Number> nums = new ArrayList<Integer>();
nums.add(5);error: incompatible types: int cannot be converted to CAP#1
where CAP#1 is a fresh type-variable:
CAP#1 extends Number from capture of ? extends Number"CAP#1" is the compiler's name for the unknown type. Read the error as "I do not know what is allowed in, so nothing is." The one value you can add is null.
? super T: a consumer you write into
The opposite wildcard has a lower bound:
static void fillWithInts(List<? super Integer> target) {
for (int i = 1; i <= 3; i++) {
target.add(i);
}
}List<? super Integer> means "a list of Integer or of some supertype of Integer". A List<Integer>, a List<Number>, and a List<Object> all qualify, and in each of them adding an Integer is safe. This matches Oracle's lower-bounded wildcard example.
The price is on the reading side. What comes out of such a list is only known to be an Object:
List<? super Integer> sink = new ArrayList<Number>();
sink.add(5); // fine
Integer x = sink.get(0); // does not compileerror: incompatible types: CAP#1 cannot be converted to Integer? alone: I do not care about the type
List<?> means a list of unknown type. You can read items as Object and ask for the size, but you cannot add anything except null. It is right for methods that only look at the elements as objects:
static void printAll(List<?> items) {
for (Object o : items) {
System.out.print(o + " ");
}
}The rule of thumb: producer extends, consumer super
Oracle's wildcard guidelines distinguish "in" and "out" variables. The popular short form is PECS: producer extends, consumer super.
| The collection... | Wildcard | You can | You cannot |
|---|---|---|---|
| produces values for you (you read) | ? extends T | read as T | add (except null) |
| consumes values from you (you write) | ? super T | add T | read as anything narrower than Object |
| both read and write | exact List<T> | read and add | accept other element types |
| type does not matter | ? | read as Object | add (except null) |
Reading real JDK signatures
Now the signatures make sense. These come from the Java SE 21 documentation:
| Signature | How to read it |
|---|---|
boolean addAll(Collection<? extends E> c) (in List<E>) | I add the elements of c into my E list, so c is a producer: it may hold any subtype of E. |
static <T> void copy(List<? super T> dest, List<? extends T> src) (Collections) | src produces Ts, dest consumes them: its element type may be T or a supertype. |
static <T extends Object & Comparable<? super T>> T max(Collection<? extends T> coll) (Collections) | The collection produces Ts. T must be comparable, and Comparable<? super T> means a T may be compared by a method defined on a supertype of T. |
boolean containsAll(Collection<?> c) (in List) | It only asks whether elements are present; the element type of c is irrelevant. |
You do not have to write the third form yourself. You only have to recognize its parts: a bound on T, a wildcard on the parameter, and ? super T inside the Comparable. It means the method accepts, for example, a collection of a subclass whose parent class implements Comparable.
Examples
Example 1: sum for any list of numbers (simple)
static double sum(List<? extends Number> nums) {
double total = 0;
for (Number n : nums) {
total += n.doubleValue();
}
return total;
}
List<Integer> ints = List.of(1, 2, 3);
List<Double> doubles = List.of(1.5, 2.5);
System.out.println(sum(ints));
System.out.println(sum(doubles));6.0
4.0One method, two list types, no copying.
Example 2: filling a more general list (practical)
List<Number> numbers = new ArrayList<>();
List<Object> objects = new ArrayList<>();
fillWithInts(numbers);
fillWithInts(objects);
System.out.println(numbers + " " + objects);[1, 2, 3] [1, 2, 3]fillWithInts(List<? super Integer>) accepts both. If its parameter were List<Integer>, passing numbers would not compile.
Example 3: collecting from several lists (realistic)
List<Number> all = new ArrayList<>();
all.addAll(ints); // a List<Integer> into a List<Number>
all.addAll(doubles); // a List<Double> into the same list
System.out.println(all);[1, 2, 3, 1.5, 2.5]This works because addAll takes Collection<? extends E>. With E = Number, both the integer list and the double list are acceptable producers. Without the wildcard in the signature, mixing sources like this would be impossible.
The same idea in your own helper:
static <T> void copyInto(List<? super T> dst, List<? extends T> src) {
for (T item : src) {
dst.add(item);
}
}copyInto(numbers, List.of(2.5)) appends 2.5 to a List<Number>.
Backend Insight
Service code moves collections between layers: a repository returns a List<Order>, a service passes orders onward, a helper totals amounts from any list of items. Wildcards in method parameters let a helper accept the most flexible input without copying and without losing type safety.
Spring and the JDK use them heavily, so you will read them in method signatures all the time, for example when passing a List<Integer> where a method takes Collection<? extends Number>. In practice, treat your own code like this: write the exact type (List<Order>) for variables and return values, and add wildcards only to method parameters where you accept input flexibly. Prefer the simpler signature when it works; wildcards add reading cost for the next programmer.
Common Mistakes
Mistake 1: Trying to add to List<? extends T>
If you need to add, the parameter should be List<? super T> or an exact List<T>. The compiler error cannot be converted to CAP#1 is the sign.
Mistake 2: Using a wildcard as a return type
A method returning List<? extends Number> forces every caller to deal with the unknown. Return the exact type, such as List<Integer>, and use wildcards on parameters.
Mistake 3: Reaching for List<Object> to accept anything
List<Object> accepts only a list whose element type is exactly Object, not a List<String>. To accept lists of any type, use List<?>.
Mistake 4: Memorizing without the reason
"Extends to read, super to write" is a good reminder, but the reason is what holds: with extends you know everything is at least a T, so reading is safe; with super you know a T fits, so writing is safe. If the rule slips, rebuild it from that.
Exercises
Try each exercise yourself before opening the solutions.
Level A: Foundation
A1. In plain words, what does List<? extends Number> accept?
Show solution A1
A1. A list whose element type is Number or any subclass of it, such as List<Integer>, List<Double>, or List<Number>.
A2. True or false: you can call add(5) on a List<? extends Number>.
Show solution A2
A2. False. The compiler does not know which exact type the list holds, so it refuses (int cannot be converted to CAP#1). Only null can be added.
A3. In Collections.copy(List<? super T> dest, List<? extends T> src), which list is read from and which is written to?
Show solution A3
A3. src is read from (a producer, ? extends T) and dest is written to (a consumer, ? super T).
Level B: Application
B1. Write static double largest(List<? extends Number> nums) that returns the largest value as a double. Call it with a List<Integer> and a List<Double>.
Show solution B1
B1.
static double largest(List<? extends Number> nums) {
double best = nums.get(0).doubleValue();
for (Number n : nums) {
if (n.doubleValue() > best) best = n.doubleValue();
}
return best;
}largest(List.of(1, 7, 3)) is 7.0 and largest(List.of(2.5, 1.5)) is 2.5.
B2. Write static void addOnes(List<? super Integer> target, int times) that adds 1 the given number of times. Call it with a List<Number> and a List<Object>.
Show solution B2
B2.
static void addOnes(List<? super Integer> target, int times) {
for (int i = 0; i < times; i++) target.add(1);
}Both List<Number> and List<Object> are accepted; after addOnes(numbers, 2) the list is [1, 1].
B3. Which lines compile?
List<Integer> ints = new ArrayList<>();
List<Number> a = ints; // line 1
List<? extends Number> b = ints; // line 2
List<Object> c = ints; // line 3
List<?> d = ints; // line 4Show solution B3
B3. Line 1 and line 3 do not compile (List<Integer> is not a List<Number> or a List<Object>). Lines 2 and 4 compile.
Level C: Challenge
C1. Write static <T> void copyInto(List<? super T> dst, List<? extends T> src) that appends every element of src to dst. Show a call that copies a List<Integer> into a List<Number>.
Show solution C1
C1.
static <T> void copyInto(List<? super T> dst, List<? extends T> src) {
for (T item : src) {
dst.add(item);
}
}
List<Number> numbers = new ArrayList<>();
copyInto(numbers, List.of(1, 2, 3)); // from a List<Integer>C2. A method needs to read numbers from a list and also add the total at the end of the same list. Explain why neither List<? extends Number> nor List<? super Integer> alone fits, and what to use.
Show solution C2
C2. With ? extends Number you can read but not add; with ? super Integer you can add but only read Object. A list that is both read and written needs its exact element type: List<Number> (or a generic <T extends Number> ... List<T> if the element type should vary), because then both operations are known to be safe.
Mini Challenge
Take the JDK signature boolean addAll(Collection<? extends E> c) and explain it in four sentences for a classmate: which collection is the producer, which is the consumer, what is allowed for c, and why new ArrayList<Number>().addAll(List.of(1, 2.5)) works.
Then write your own static double total(Collection<? extends Number> items) and call it with a List<Integer>, a Set<Double>, and a List<Number>.
Check your work: you named the wildcard direction (extends for reading), and none of your three calls needed a cast.
Summary & Next Lesson
Key takeaways
List<Integer>is not aList<Number>; wildcards express flexibility safely.? extends Tis for reading (a producer): you can read asT, not add.? super Tis for writing (a consumer): you can addT, but read onlyObject.?alone means the element type does not matter.- Use PECS: producer extends, consumer super. Put wildcards on parameters, not return types.
- Signatures such as
addAll(Collection<? extends E>)andcopy(List<? super T>, List<? extends T>)follow this pattern.
Next lesson
Next, "Demo: building a generic in-memory repository" puts the module together: a generic class with two type parameters, used for two unrelated types, with the compiler guarding every call.
Self-check
- Why is
List<Integer>not aList<Number>? - Why can you not add to a
List<? extends Number>? - What does
? super Integerallow you to do? - What does PECS stand for?
- When is
List<?>the right parameter type?
Show answers
- If it were, you could add a
Doublethrough theList<Number>view and corrupt the integer list. - The exact element type is unknown, so no value other than
nullis known to be safe. - Add
Integervalues, because the list holdsIntegeror a supertype. Reading gives onlyObject. - Producer extends, consumer super.
- When the method only treats elements as
Objector ignores them, such as printing or checking the size.
References
- Oracle, The Java Tutorials: Upper Bounded Wildcards. Supports the meaning of
List<? extends Number>and thesumOfListexample. - Oracle, The Java Tutorials: Lower Bounded Wildcards. Supports
List<? super Integer>and the in/out guideline. The acronym PECS is a widely used name for that guideline, not Oracle's term. - Oracle, The Java Tutorials: Generics, Inheritance, and Subtypes. Supports that
List<Integer>is not aList<Number>. - Oracle, Java SE 21 API: Collections (first part of the page only; later parts were not read). Supports the signatures of
max,copy, andsort. - Oracle, Java SE 21 API: List. Supports the signatures of
addAllandcontainsAll. - All code and compiler messages were compiled and run with JDK 21. The plain-language reading of
Comparable<? super T>is the course's own explanation, not a quote from the documentation.
Practice
Apply this lesson with the module's lab, exercises and quiz.
Marking a lesson complete is optional and you can undo it any time. Your progress stays in this browser.