Drop of Java - Generics and the PECS rule: using bounded wildcards to increase API flexibility

1,799 views
Skip to first unread message

Mario Fusco

unread,
Jan 11, 2009, 5:31:36 PM1/11/09
to juglugano
The most important difference between Arrays and Lists of generics is
that while the first one are covariant the second one are invariant.
These two scary-sounding words actually mean something very easy to
understand: if a class Sub is a subtype of another class Super then
the array type Sub[] is a subtype of Super[], but a List<Sub> is not a
subtype of List<Super>. Under this point of view, while it may seem
that arrays are more flexible, in reality generics allow to write
safer code. In fact the following code fragment is legal but throws a
runtime exception:

Object[] objectArray = new Long[1];
objectArray[0] = "I don't fit in"; // Throws ArrayStoreException

but this one don’t compile at all:

List<Object> ol = new ArrayList<Long>(); // Incompatible types
ol.add("I don't fit in");

The flexibility and reusability that apparently are missing in the
generics implementation can be recovered (where needed) by using the
so called bounded wildcards. Joshua Bloch (who leaded the expert group
that defined the generics specifications) in his speech at last Devoxx
suggested to use wildcard types on method’s input parameters that
represent producers or consumers, following what he called the PECS
rule:

PECS stands for producer-extends, consumer-super

In other words, if the type of a method parameter is a generic of E
that represents a producer of E (it produces objects used in the
method execution), use <? extends E>; conversely if it represents a
consumer of E (it consumes the object produced by the method), use <?
super E>.

In the example proposed by Bloch to justify this rule he considered a
Stack class that provides the following public API:

public class Stack<E> {
public Stack();
public void push(E e);
public E pop();
public boolean isEmpty();
}

Supposing that we want to add a method that takes a sequence of
elements and pushes them all onto the stack:

public void pushAll(Iterable<E> src) {
for (E e : src) push(e);
}

This method compiles and does its job correctly but, under the point
of view of who needs to use it, appears not flexible enough. An
example can easily show why:

Stack<Number> numberStack = new Stack<Number>();
Iterable<Integer> integers = Arrays.asList(1, 2, 3);
numberStack.pushAll(integers);

Despite this code seems a straightforward use of the Stack class, it
won’t compile at all, because, as stated, Iterable<Integer> is not a
subclass of Iterable<Number>. To deal with situations like this, the
language provides a special kind of parameterized type called bounded
wildcard type. In order to make the pushAll() method of our example
more client-friendly, we can change the type of its input parameter
from “Iterable of E” to “Iterable of some subtype of E”. The wildcard
type that means precisely that is Iterable<? extends E> and so it’s
possible to modify that method as it follows:

public void pushAll(Iterable<? extends E> src) {
for (E e : src) push(e);
}

This change allows the former client code to compile and run
correctly. In the same way let’s add a popAll() method to the Stack
class that pops each element off the stack and adds them to a given
collection:

public void popAll(Collection<E> dst) {
while (!isEmpty()) dst.add(pop());
}

Again this method’s implementation works but suffers of poor
flexibility, not allowing the following client code to compile:

Stack<Number> numberStack = new Stack<Number>();
Collection<Object> objects = new ArrayList<Object>();
numberStack.popAll(objects);

In this case the input parameter type should not be “collection of E”
but “collection of some supertype of E” and we can achieve it through
a wildcard type meaning precisely that: Collection<? super E>. Let’s
modify popAll() to use it:

public void popAll(Collection<? super E> dst) {
while (!isEmpty()) dst.add(pop());
}

This easy example shows that 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 and that is also the
reason why you should never 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.

Luca S Lopomo

unread,
Jan 12, 2009, 1:48:27 PM1/12/09
to juglugano
+1

lopo
Reply all
Reply to author
Forward
0 new messages