Friday, May 23, 2008

PECS in Action

One funny bit at JavaOne was Josh Bloch introducing the mnemonic PECS. Well actually what was most funny about it was the picture of Arnold Schwarzenegger that accompanied it... Anyways, PECS stands for producer-extends, consumer-super. Instead of repeating what Josh said or plagiarizing Effective Java, I will give an example of how PECS helped me out yesterday.

One there was an API that existed back in olden times, before Java 1.5 It looked like this:

void runInboundCycles(final Module[] modules)

There was also a runOubound, but you get the picture. The class Module is an interface that has many implementations. This API got tweaked courtesy of Java 1.5:

void runInboundCycles(final List<Module> modules);

Before the change you could do this:

runInboundCycles(new Module[] { new MyModule() } );

A logical uprev would be:

runInboundCycles(Arrays.asList(new MyModule()));

Turns out that won't compile! The Arrays.asList call will return a List and the Java compiler says that is not a List. So instead you have to do something annoying like:

List<Module> modules = new ArrayList<Module>(1);
modules.add(new MyModule());

This is particularly annoying if modules is actually a member variable, as now you cannot declare it to be final. Enter PECS.

My API is using the Modules, thus my parameter is a producer to the API. Remember producer-extends, so refactor the API like this:

void runInboundCycles(List<? extends Module> modules);

Now you can pass a List<MyModule> and the compiler won't complain.

There are a couple of things about this that bother me. When my crazy brain looks at the API, it thinks "the API consumes Modules." Maybe that's just me. The other thing that bothers me is writing ? extends Module because Module is an inteface. Now granted it would suck to have to write ? implements Module just because Module is an interface and write ? extends Module just because Module was a class, so I am not advocating the altnernative. It just feels weird to write extends in front of an interface type. Maybe I have been programming in Java too long.


Jesper said...

Valid point about the non-obviousness about seeing the API as the consumer/producer. What works for me is to consider the parameter itself (instead the API), as in the following signature:

void copy(Collection < ? extends Animal> source, Collection < ? super Animal> dest);

PECS says: Source is clearly a producer, dest is the consumer. This makes it clearer to me, at least.

I think the "extends"-bit works fine in front of an interface name, just like in derived interfaces, but the questionmark is kindda odd.

Mouneer said...

You could argue that an interface is nothing more but the general idea of an abstract class modulo the implementation. Using the term "extends" in conjunction with interfaces seems to be a valid point then.

But what if you pass in the exact type E that is part of your generict method signature?

Then you have ?=E => ? extends E = E extends E. So E must be a subclass of itself? Weird.