As noted in Item
25, parameterized types are invariant. In other
words, for any two distinct types Type1
and
Type2, List<Type1>
is
neither a subtype nor a supertype of List<Type2>. While it is
counterintuitive that List<String> is not a
subtype of List<Object>, it really
does make sense. You can put any object into a List<Object>, but you can
put only strings into a List<String>. Sometimes
you need more flexibility than invariant typing can provide. Consider the stack
from Item 26. To refresh your memory, here is its public API:
public class Stack<E> {
public Stack();
public void push(E e);
public E pop();
public boolean isEmpty();
}
Suppose we want
to add a method that takes a sequence of elements and pushes them all onto the
stack. Here’s a first attempt:
// pushAll method without wildcard type - deficient!
public void pushAll(Iterable<E> src) {
for (E e : src)
push(e);
}
This method
compiles cleanly, but it isn’t entirely satisfactory. If the element type of
the Iterable src exactly
matches that of the stack, it works fine. But suppose you have a Stack<Number> and you invoke push(intVal), where intVal
is
of type Integer. This works,
because Integer is a subtype
of Number. So
logically, it seems that this should work, too:
Stack<Number> numberStack = new
Stack<Number>();
Iterable<Integer> integers = ... ;
numberStack.pushAll(integers);
If you try it,
however, you’ll get this error message because, as noted above, parameterized
types are invariant:
StackTest.java:7: pushAll(Iterable<Number>) in
Stack<Number>
cannot be applied to (Iterable<Integer>)
numberStack.pushAll(integers);
^
Luckily,
there’s a way out. The language provides a special kind of parameterized type
call a bounded wildcard type to deal with
situations like this. The type of the input parameter to pushAll should not be “Iterable
of
E” but “Iterable of some subtype of E,” and there is a wildcard type that means precisely that: Iterable<? extends E>.
// Wildcard type for parameter that serves as an E
producer
public void pushAll(Iterable<?
extends E> src) {
for (E e : src)
push(e);
}
Now suppose
you want to write a popAll method to go
with pushAll. The popAll method pops each element off the stack and
adds the elements to the given collection. Here’s how a first attempt at
writing the popAll method might
look:
// popAll method without wildcard type - deficient!
public void popAll(Collection<E> dst) {
while (!isEmpty())
dst.add(pop());
}
Again, this
compiles cleanly and works fine if the element type of the destination
collection exactly matches that of the stack. But again, it doesn’t seem
entirely satisfactory. Suppose you have a Stack<Number>
and
variable of type Object. If you pop
an element from the stack and store it in the variable, it compiles and runs
without error. So shouldn’t you be able to do this, too?
Stack<Number> numberStack = new
Stack<Number>();
Collection<Object> objects = ... ;
numberStack.popAll(objects);
If you try to
compile this client code against the version of popAll
above,
you’ll get an error very similar to the one that we got with our first version
of pushAll: Collection<Object> is not a subtype of Collection<Number>. Once again, wildcard types
provide a way out. The type of the input parameter to popAll should not be “collection of E” but “collection of some supertype of E” (where
supertype is
defined such that E is a supertype of itself [JLS,
4.10]). Again, there is a wildcard type that means precisely that: Collection<? super E>. Let’s modify
popAll to use it:
// Wildcard type for parameter that serves as an E
consumer
public void popAll(Collection<?
super E> dst) {
while (!isEmpty())
dst.add(pop());
}
With this
change, both Stack and the client
code compile cleanly.
For maximum
flexibility, use wildcard types on input parameters that represent producers or
consumers.
Here is a
mnemonic to help you remember which wildcard type to use: PECS stands for
producer-extends, consumer-super.
In other words,
if a parameterized type represents a T
producer,
use <? extends T>; if it
represents a T consumer, use <? super T>. In our Stack
example,
pushAll’s src parameter produces E instances for use by the Stack, so the appropriate type for src is Iterable<?
extends E>; popAll’s dst parameter consumes E instances from the Stack, so the appropriate type for dst is Collection<?
super E>. The PECS mnemonic captures the fundamental principle that guides
the use of wildcard types. Naftalin and Wadler call it the Get and Put Principle [Naftalin07, 2.4].
Now let’s look
at the union method from Item
27. Here is the declaration:
public static <E> Set<E> union(Set<E>
s1, Set<E> s2)
Both
parameters, s1 and s2, are E
producers,
so the PECS mnemonic tells us that the declaration should be:
public static <E> Set<E> union(Set<?
extends E> s1,
Set<? extends E> s2)
Note that the
return type is still Set<E>. Do not use
wildcard types as return types. Rather than providing additional flexibility
for your users, it would force them to use wildcard types in client code.
Properly used,
wildcard types are nearly invisible to users of a class. They cause methods to
accept the parameters they should accept and reject those they should reject. If the user of a
class has to think about wildcard types, there is probably something wrong with
the class’s API.
Here is a
revised declaration that uses wildcard types:
public static <T extends Comparable<? super T>> T max(List<?
extends T> list)
To get the
revised declaration from the original one, we apply the PECS transformation
twice. The straightforward application is to the parameter list. It produces T
instances,
so we change the type from List<T>
to
List<? extends T>. The tricky
application is to the type parameter T. This is the
first time we’ve seen a wildcard applied to a type parameter. T was originally specified to extend Comparable< T>, but a comparable of T consumes T
instances
(and produces integers
indicating
order relations). Therefore the parameterized type Comparable<T> is replaced by the bounded
wildcard type Comparable<? super T>. Comparables
are always consumers, so you should always use Comparable<? super T> in preference to
Comparable<T>. The same is
true of comparators, so you should always use Comparator<? super T> in preference to
Comparator<T>.
In summary,
using wildcard types in your APIs, while tricky, makes the APIs far more
flexible. If you write a library that will be widely used, the proper use of
wildcard types should be considered mandatory. Remember the basic rule:
producer- extends, consumer-super (PECS). And remember that all comparables and
comparators are consumers.
Reference: Effective Java 2nd Edition by Joshua Bloch