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
List<Module> modules = new ArrayList<Module>(1);
modules.add(new MyModule());
runInboundCycles(modules);
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.
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:
ReplyDeletevoid 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.
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.
ReplyDeleteBut 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.