Saturday, July 10, 2010

Generics PECS principle, step by step

I will open this post with a disclaimer -
I know Joshua Bloch discusses PECS in his book "Effective Java" (or at least, this is what I heard from people who read the book), and yet - after hearing a lecture about PECS and reading other posts about it, I felt I would like to explain more about the issue.

First of all - what is PECS?
PECS stands for "Producer Extends, Consumer Super" - and it addresses two keywords in context of generics usage: "super" and "extends"

For the sake of the post, let's assume the following is our class hierarchy:




If we take a look at the following list definition and usage:


We can see that we defined animals to be a list of a type that we are not aware of during the definition, but we can say that the type extends Animal.
Therefore, the following lines make only sense - lists of Animal, SeaAnimal and Fish correspond to that definition.

If we go out of the scope of generics, and look at the following code, we can see some similarity:



The dynamic type of "a" is Animal, therefore we can initialize it with any concrete subclasses of Animal.

In order to understand "super" in context of Generics, let's first say with the above example, and look at the following method:



If we would like to set the result of the method in an object (without the usage of a downcast), what can be the type of the object?
The type of the object can be any of the ancestors (super classes) of SeaAnimal, and of course, SeaAnimal itself.

For example:



The keyword "super" is used in a similar way. In the above example, we wanted to consume the result of the method "produceSeaAnimal" to the object "a".

If we take a look at the following method:


And the following usage:


We can see that we are consuming a list of sea animals that are is produced by the method "procudeSomeSeaAnimals" in a slightly different manner than in the case of "produceSeaAnimal" - this is due to a limitation in the generic mechanism that prohibits us from defining the method "produceSomeSeaAnimals" the following way:


and yet, the principal is similar - when we consume the result of the method, the producer should have a contract that defines the consumer information on the bottom level of the class hierarchy it can consume.

The last example will demonstrate usage of "super" and "extends" together - implementing the PECS mechanism in one method call:



And possible usages of this methods:


Which shows us we can pass as input lists that their generic types extend SeaAnimal (as explained in the beginning of the post) to the producer, and we can consume a list that their generic types are either SeaAnimal or a super class of it (as explained above)

4 comments: