With a class hierarchy below,
class Fruit {
public void hello() {}
}
class Apple extends Fruit {}
class Banana extends Fruit {}
Lower Bounded Wildcards and Upper Bounded Wildcards
? super Fruit
is called lower bounded wildcard.
? extends Fruit
is called upper bounded wildcard.
Usually a class hierarchy is illustrated like,
Object
|
Fruit
/ \
Apple Banana
The base class is placed upper, subclasses placed lower.
For ? super Fruit
, the lower bound of the type argument is determined, which is Fruit
, so it’s called lower bounded wildcard.
Similar, for type argument ? extends Fruit
, its upper bound is fixed, so it’s called upper bounded wildcard.
”? super Fruit”
void func(List<? super Fruit> fruits) {
fruits.add(new Fruit()); // ok
fruits.add(new Apple()); // ok
fruits.add(new Banana()); // ok
// Error: Cannot resolve method 'hello'
fruits.get(0).hello();
}
The ?
in the declaration List<? super Fruit>
means fruits
is a List
of an unknown type.
The ? super Fruit
means the unknown type is limited to be Fruit
or its super class (Object
in this case).
When invoke func(List<? super Fruit> )
, List<Fruit>
or List<Object>
can be passed as the argument.
All the fruits.add(...)
in the above code is ok, because no matter what type actually the type argument is
Fruit
, Apple
, and Banana
are the subtypes of that type.
An Apple
object is a Fruit
(or Object
) object, so an Apple
object can be added into a List
of Fruit
(or Object
).
Why fruits.get(0).hello()
fails?
The actual type of the elements in the fruits
List is undetermined when declare the method.
When call a method on an element, the compiler needs to make sure the unknown type has the method.
? super Fruit
in the type argument declaration sets bounds for the unknown type, one bound (“lower”) is type Fruit
, the other bound (“upper”) is type Object
To safely invoke methods on the unknown type, only methods of the most upper type can be invoked.
Obviously, method hello
is not from the most upper type (i.e. Object
).
If passing a List<Object>
as the parameter, then obviously the elements in fruits
would not have hello()
method. Therefore the compiler raises the error.
”? extends Fruit”
void func(List<? extends Fruit> fruits){
// Error: add(capture<? extends Fruit>) in List cannot be applied to add(Fruit)
fruits.add(new Fruit());
fruits.add(new Apple()); // similar error
fruits.add(new Banana()); // similar error
fruits.get(0).hello(); // ok
}
Similar, declaring the parameter as List<? extends Fruit>
means fruits
is an unknown type,
and can be List<Fruit>
, List<Apple>
or List<Banana>
when the method is invoked.
Why fruits.get(0).hello()
is ok in this case?
Similar, ? extends Fruit
sets bounds for the unknown type, the upper bound is determined as the type Fruit
.
The hello()
method is from the most upper type, so the compiler knows it’s safe to call it within the func
body.
Why statements like fruits.add(new Fruit())
fails?
The type of fruits
is unknown, it doesn’t mean the type of fruits
is “dynamic”.
Being “dynamic” means the type of fruits
can be List<Fruit>
at some point, then be List<Apple>
at some other point.
It’s not possible in Java, since Java is a static language.
The two types, List<Fruit>
and List<Apple>
, have nothing to do with each other
(List<Apple>
is not a subtype of List<Fruit>
).
The type of the fruits
parameter is determined on the invocation of func
.
For a specific invocation, the type of fruits
is determined and static.
For example, with an invocation like func(new ArrayList<Apple>())
, the statement fruits.add(new Fruit())
would raise compiler error
(since a Fruit
cannot be add()
ed into a List<Apple>
).
To ensure all the possible invocation of func
works, the compiler just can’t allow the statements like fruits.add(new Fruit())
appear in the method body.
What is “capture” (capture<? extends Fruit>
)?
When compile the line fruits.add(new Fruit())
in IntelliJ, IntelliJ reports error message like
Error: add(capture<? extends Fruit>) in List cannot be applied to add(Fruit)
With plain javac (1.8.0_131), it reports error like
Fruit.java:8: error: no suitable method found for add(Fruit)
fruits.add(new Fruit());
^
...
method List.add(CAP#1) is not applicable
(argument mismatch; Fruit cannot be converted to CAP#1)
...
where CAP#1 is a fresh type-variable:
CAP#1 extends Fruit from capture of ? extends Fruit
Though the fruits
is declared as an unknown type (List<? extends Fruit>
), the compiler sees it as of type List<CAP#1>
,
where the type CAP#1
is a subclass of Fruit
and is captured on an invocation of func
.
Clearly, a type is not assignable to its subtypes (“Fruit cannot be converted to CAP#1”),
therefore the compiler raises error.
Why not just List<Fruit>
void func(List<Fruit> fruits) {
fruits.add(new Fruit()); // ok
fruits.add(new Apple()); // ok
fruits.add(new Banana()); // ok
fruits.get(0).hello(); // ok
}
It looks that declaring fruits
as List<Fruit>
fixes all the above problems.
However, with func
declared like this, it now accepts less types of the parameter than the two other forms above.
List<Apple> apples;
func(apples); // error, incompatible types
The type List<Apple>
is not a subtype of List<Fruit>
, so func(apples)
raises compile error.
While for func(List<? extends Fruit>)
, the type List<Apple>
is a subtype of List<? extends Fruit>
,
so it can accept a parameter of type List<Apple>
.
Wildcard and API Design
When design an API, usually you want to the API is as flexible as possible.
Take boolean addAll(Collection<? extends E> c)
in java.util.List
as an example.
If addAll
is declared as addAll(Collection<E> c)
, then code below would not pass compilation.
List<Fruit> fruits = new ArrayList<>();
List<Apple> apples = ...;
fruits.addAll(apples);
PECS (Producer Extends Consumer Super)
public class Foo<T> {
public void consume(T t) {}
public T produce() {
T t;
// ...
return t;
}
}
Here is a generic type Foo
.
Foo
has a method accepts a parameter of its type parameter T
.
For such a method, we see it as the method “consumes” objects of type T
.
On the other side, for methods returns an object of type T
, we see it as a “produce method”.
When design methods (API) taking the generic type Foo
as their arguments, we can use the PECS principle.
If in the body of a method, only “produce methods” of the generic type Foo
are used, then we should declare
the method’s argument as type Foo<? extends Fruit>
.
void func(Foo<? extends Fruit> foo) {
foo.produce();
foo.product().hello();
}
If in the body of a method, only “consume methods” of Foo
are used, then the method’s argument should be declared
as type Foo<? super Fruit>
.
void func(Foo<? super Fruit> foo) {
Fruit fruit;
Apple apple;
foo.consume(fruit);
foo.consume(apple);
}
By adopting the PECS principle, the method/API allows its parameter being of more types.
An example of PECS adoption can be found in java.util.List
interface.
boolean addAll(Collection<? extends E> c)
is an example of “PE”.
We can image that addAll
is implemented as below.
boolean addAll(Collection<? extends E> c) {
for (int i = 0; i < c.size(); ++i) {
E e = c.get(); // get() is a "produce method"
// add e into the list, omitted...
}
}
void sort(Comparator<? super E> c)
is an example of “CS”. And assume it’s implemented as below.
void sort(Comparator<? super E> c) {
while(not the list not fully sorted) {
// find two elements to compare
E e1;
E e2;
int result = c.compare(e1, e2); // compare is a "consume method"
// sort depending on the result ...
}
}
List<Apple> apples;
Comparator<Fruit> comparator;
apples.sort(comparator);
Let’s say Comparator<Fruit>
sorts Fruit
by weight, Comparator<Apple>
sorts Apple
s by weight and how red an Apple
is.
It’s reasonable to sort List<Apple>
by the common fruit comparator.
The “CS” principle makes the API supports it.