Appearance
The super
Wildcard
We previously discussed the inheritance relationship of generics: Pair<Integer>
is not a subclass of Pair<Number>
.
Let's examine the following set
method:
java
void set(Pair<Integer> p, Integer first, Integer last) {
p.setFirst(first);
p.setLast(last);
}
Passing Pair<Integer>
is allowed, but passing Pair<Number>
is not.
In contrast to the extends
wildcard, this time we want to accept Pair<Integer>
types, as well as Pair<Number>
and Pair<Object>
, because Number
and Object
are superclasses of Integer
, and setFirst(Number)
and setFirst(Object)
effectively allow accepting Integer
types.
We use the super
wildcard to rewrite this method:
java
void set(Pair<? super Integer> p, Integer first, Integer last) {
p.setFirst(first);
p.setLast(last);
}
Note that Pair<? super Integer>
means the method parameter accepts any Pair
type whose generic type is Integer
or a superclass of Integer
.
The following code can be compiled without errors:
java
public class Main {
public static void main(String[] args) {
Pair<Number> p1 = new Pair<>(12.3, 4.56);
Pair<Integer> p2 = new Pair<>(123, 456);
setSame(p1, 100);
setSame(p2, 200);
System.out.println(p1.getFirst() + ", " + p1.getLast());
System.out.println(p2.getFirst() + ", " + p2.getLast());
}
static void setSame(Pair<? super Integer> p, Integer n) {
p.setFirst(n);
p.setLast(n);
}
}
class Pair<T> {
private T first;
private T last;
public Pair(T first, T last) {
this.first = first;
this.last = last;
}
public T getFirst() {
return first;
}
public T getLast() {
return last;
}
public void setFirst(T first) {
this.first = first;
}
public void setLast(T last) {
this.last = last;
}
}
Examining the setFirst()
method of Pair<? super Integer>
, its method signature is effectively:
java
void setFirst(? super Integer);
Thus, it is safe to pass an Integer
type.
Now, let's look at the getFirst()
method of Pair<? super Integer>
, whose method signature is:
java
? super Integer getFirst();
Here, we note that we cannot use an Integer
type to receive the return value of getFirst()
, meaning the following statement will not compile:
java
Integer x = p.getFirst();
This is because if the actual type passed is Pair<Number>
, the compiler cannot cast Number
to Integer
.
Note: Although Number
is an abstract class and cannot be instantiated directly, even if Number
were not abstract, this would still not compile. Additionally, when passing a Pair<Object>
type, the compiler cannot cast Object
to Integer
.
The only type that can receive the return value of getFirst()
is Object
:
java
Object obj = p.getFirst();
Therefore, using <? super Integer>
indicates:
- It allows calling the
set(? super Integer)
method to pass in a reference ofInteger
. - It does not allow calling the
get()
method to obtain a reference ofInteger
, with the only exception being retrieving anObject
reference:Object o = p.getFirst()
.
In other words, using <? super Integer>
as a method parameter means the method's internal code can only write to the parameter, but cannot read from it.
Comparing extends
and super
Wildcards
Let’s revisit the extends
wildcard. The differences between <? extends T>
and <? super T>
as method parameters are:
<? extends T>
allows calling the read methodT get()
to obtain a reference ofT
, but does not allow calling the write methodset(T)
to pass in a reference ofT
(except fornull
).<? super T>
allows calling the write methodset(T)
to pass in a reference ofT
, but does not allow calling the read methodT get()
to obtain a reference ofT
(except for obtaining anObject
).
One allows reading but not writing, while the other allows writing but not reading.
Keep the above conclusions in mind as we look at the copy()
method defined in the Java standard library's Collections
class:
java
public class Collections {
// Copies each element from src to dest:
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
for (int i = 0; i < src.size(); i++) {
T t = src.get(i);
dest.add(t);
}
}
}
This method copies each element from one List
to another. The first parameter is List<? super T>
, representing the destination list, while the second parameter is List<? extends T>
, representing the source list. We can simply implement the copy using a for
loop. In the loop, we see that for the variable of type <? extends T>
, src
, we can safely obtain a reference of type T
, while for the variable of type <? super T>
, dest
, we can safely pass in a reference of T
.
The definition of this copy()
method perfectly illustrates the intent of extends
and super
:
- The
copy()
method does not read fromdest
, since it cannot calldest.get()
to obtain a reference ofT
. - The
copy()
method does not modifysrc
, since it cannot callsrc.add(T)
.
This is enforced by the compiler. If the method code unexpectedly modifies src
or unexpectedly reads from dest
, it will result in a compilation error:
java
public class Collections {
// Copies each element from src to dest:
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
...
T t = dest.get(0); // compile error!
src.add(t); // compile error!
}
}
Another benefit of this copy()
method is that it allows safely adding a List<Integer>
to a List<Number>
, but not the other way around:
java
// Copy List<Integer> to List<Number> - ok:
List<Number> numList = ...;
List<Integer> intList = ...;
Collections.copy(numList, intList);
// ERROR: cannot copy List<Number> to List<Integer>:
Collections.copy(intList, numList);
All of this is achieved through the super
and extends
wildcards, enforced by the compiler.
PECS Principle
When to use extends
and when to use super
? To help remember, we can use the PECS principle: Producer Extends Consumer Super.
This means that if you need to return T
, it is a producer, and you should use the extends
wildcard; if you need to write T
, it is a consumer, and you should use the super
wildcard.
Taking the copy()
method in Collections
as an example:
java
public class Collections {
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
for (int i = 0; i < src.size(); i++) {
T t = src.get(i); // src is a producer
dest.add(t); // dest is a consumer
}
}
}
Since src
, which needs to return T
, is a producer, it is declared as List<? extends T>
. Conversely, since dest
, which needs to write T
, is a consumer, it is declared as List<? super T>
.
Unbounded Wildcard
We have discussed the roles of <? extends T>
and <? super T>
as method parameters. In fact, Java generics also allow the use of unbounded wildcards (Unbounded Wildcard Type), which is simply defined as ?
:
java
void sample(Pair<?> p) {
}
Since the <?>
wildcard has neither extends
nor super
, it has the following restrictions:
- You cannot call the
set(T)
method with a reference (except fornull
). - You cannot call the
T get()
method to retrieve aT
reference (you can only get anObject
reference).
In other words, you cannot read or write, so you can only perform some null checks:
java
static boolean isNull(Pair<?> p) {
return p.getFirst() == null || p.getLast() == null;
}
In most cases, you can introduce a generic parameter <T>
to eliminate the <?>
wildcard:
java
static <T> boolean isNull(Pair<T> p) {
return p.getFirst() == null || p.getLast() == null;
}
The <?>
wildcard has a unique characteristic: Pair<?>
is a supertype of all Pair<T>
:
java
public class Main {
public static void main(String[] args) {
Pair<Integer> p = new Pair<>(123, 456);
Pair<?> p2 = p; // Safe upcasting
System.out.println(p2.getFirst() + ", " + p2.getLast());
}
}
class Pair<T> {
private T first;
private T last;
public Pair(T first, T last) {
this.first = first;
this.last = last;
}
public T getFirst() {
return first;
}
public T getLast() {
return last;
}
public void setFirst(T first) {
this.first = first;
}
public void setLast(T last) {
this.last = last;
}
}
The above code compiles and runs correctly because Pair<Integer>
is a subclass of Pair<?>
, allowing for safe upcasting.
Summary
Using a wildcard like <? super Integer>
as a method parameter indicates:
- The method can call methods that accept an
Integer
reference, for example:obj.setFirst(Integer n);
. - The method cannot call methods that retrieve an
Integer
reference (except forObject
), for example:Integer n = obj.getFirst();
.
In other words, using the super
wildcard means you can write but not read.
When using extends
and super
wildcards, you should follow the PECS principle.
The unbounded wildcard <?>
is rarely used and can often be replaced with <T>
, and it serves as a supertype for all types <T>
.