Saturday 7 July 2012

Item 28: Use bounded wildcards to increase API flexibility


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