Dec 4, 2012

Java Generics and PECS Rule

Java Generics and PECS Rule
The fad behind the Arrays and the generic Collections is defined by this two important scenarios: Arrays are covariant(Widening their reference) while Collections are invariants(Narrowing their reference). When it comes to input parameters it becomes clear that these two containers differ largely and requires extra careful approach. for instance if a class of Foo is a subtype of another class Foobar then the array of type Foo[] is a subtype of Foobar[].

But in case of Collection, the List of Foo is not a subtype of List of Foobar, because the Collections are invariant. Arrays shows more flexibility here than List of Collections. But with Generics Collections can be clean type safe.

 Examples:
//not flexible, won't compile
List<Apple> apples = new ArrayList <Apple>();
List<Fruit> fruits = apples;
//very flexible and will compile
List<Apple> apples = new ArrayList<Apple>();
List<? extends Fruit> fruits = apples;
//not flexible, won't compile
List<Fruit> fruits = new ArrayList<Fruit>();
List<Apple> apple = fruits;
//very flexible and will compile
List<Fruit> fruits = new ArrayList<Fruit>();
List<? super Apple> apple = fruits;
The solution to the complexity regarding the input parameter types, was solved by the leading expert and technical leader of generics specifications, Joshua Bloch. in his speech at Devoxx, suggested the use of wildcard types on method’s input parameters where the producer class extends object subtype(objects used in the method execution) while the super class consumes its object(ie consumes the object produced by the method). Puting this in a simple mnemonic called PECS. 
PECS stands for producer-extends, consumer-super. In the example proposed by Josh Bloch to justify this rule he considered a Stack class that provides the following public API:
public class Stack {
        public Stack();
        public void push(E e);
        public E pop();
        public boolean isEmpty();
}
For example we want to add a method that takes a sequence of elements and push all of them into the stack, we use pushAll():
public void pushAll(Iterable<E> src) {
        for (E e : src) push(e);
}
The method may look okey, it also may also compiles on certain situation depending on the context in which it is used. But it lacks flexibility at all. The following implementation tells why is it not flexible:
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, Iterable<Integer> is not a subclass of Iterable<Number>. To deal with situations like this, the Java language provides a special kind of parameterized type called bounded wildcard type. In order to make the pushAll() method of stack example more flexible, we can change the type of its input parameter from “Iterable of E” to “Iterable of some subtype of E”. Thus we modify our method as 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. Similarly we can add the popAll() method to the satck class which pops every single element off the stack and adds them to a specific collection.
public void popAll(Collection<E> dst) {
        while (!isEmpty()) dst.add(pop());
}
As the previous example, this method is not flexible as well and may cause the client code fail in some cases. The following implementation code would not compile as a result.
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 rather the “collection of some supertype of E” and we can achieve that through a wildcard type as we did in the first instance:
public void popAll(Collection<? super E> dst) {
        while (!isEmpty()) dst.add(pop());
}
In these instances we realized that some APIs can really be inflexible and not always safe for all client codes especially when not designed with the flexibility in mind in the first place. The parameterized input types can be very complex some times and totally invisible to the class that may use them. PECS rule certainly would address these concerns. This is a question of good API design and making code more flexible and less error prone.

1 comment:

  1. And more left to say, bounded wildcard is not used in that regard to return value from a method :)

    ReplyDelete