Michel said:
But since ServiceComponent is an abstract class or interface, in
practice, is'nt Service<? extends ServiceComponent> a "subset" of
Service<ServiceComponent> ? Therefore, is'nt the cast from the later
into the former safe?
This is a surprisingly common generics question (given it's answered
darn near everywhere and every week here), and the answer, incidentally,
is "no".
Foo<X> and Foo<? extends X> are not subtypes of each other, because of
example cases like this:
List<Object> lo = new ArrayList<Object>();
lo.add("foo"); // OK, a String is an Object.
List<Integer> li = lo; // Fortunately won't work.
Integer i = li.iterator().next(); // Uh-oh.
List<Integer> li = new ArrayList<Integer>();
List<Object> lo = li; // Fortunately won't work.
lo.add("foo");
Integer i = li.iterator().next(); // Uh-oh.
Notice that either case allows us to wind up pulling a String from a
List<Integer>! If we can get the same list referenced as a List<Object>
and a List<Integer> we can put a non-Integer in via the former and pull
out a nice fat ClassCastException via the latter. So much for
compile-time type safety.
Of course, a List<Integer> is a List<? extends Object>. And a
List<Object> is a List<? super Integer>.
The type safety rules basically don't let us put anything into a List<?
extends Object> but we can read out Objects, and don't let us retrieve
from a List<? super Integer> but we can put things in. Neither List
sounds useful, but if we have a List<Integer> and pass it somewhere
expecting a List<? extends Object> the latter can read our integers, and
we can still put things in via the reference of type List<Integer>. The
latter doesn't know they are integers, mind you, but it can count them,
invoke all their toString methods, or whatever. Likewise if we pass a
List<Object> to something expecting a List<? super Integer> that
something can dump Integers into the list (and it can actually read
Objects out, since whatever's in the list has to extend Object, but
anything more requires an explicit cast).
This comes in useful once you write generic stuff. Consider a game with
a GameObject class. The instances are all things like game enemies,
powerups, and even the player that can be drawn on the screen and can
potentially move around and stuff. The GameObject class is polymorphic,
and subclasses like Powerup don't do anything in their move() methods
while others like Enemy use AI and Player uses the input device to
decide where to move. (The input device might even be abstracted as a
Connection, and you've got no problem later adding online multiplayer!
Just add a NetworkConnection to the existing ConsoleConnection as
concrete Connection types.) Now we want some generic display functions
that display GameObjects from a List. One displays them in the main area
at their proper positions; another displays them at particular
coordinates in a grid in a corner area to show the graphics for
currently active powerups.
The first takes a List<GameObject> and is basically
for (GameObject go : list) { go.display(go.getX(), go.getY() }
The second takes a Set<Powerup>, since that's what our powerup state is,
with objects added as they are acquired and removed when they run out.
It does
x = 0; y = 0; for (Powerup p : list) { p.display(x, y); /* some logic to
move x and y*/ }
Now we want to add that networked multiplayer and show the players'
graphics in a scoreboard list from time to time. Why create a new
function for this when players form a Set and we already have a
Set<Powerup> displaying method? Let's change it to accept a Set<GameObject>.
Oops. Ah yes, that's why we had it a Set<Powerup> to begin with; we
can't assign our Powerup set to a Set<GameObject>. Or our Set<Player>
either for that matter.
But it's perfectly safe; the loop doesn't add anything to the Set after
all! That's why we want:
public void drawInGridAt
(int x, int y, int size, Set<? extends GameObject> objects) {
// some logic setting i and j up
for (GameObject go : objects) {
go.display(x + size*i, y + size*j);
// some logic changing i and j
}
}
Presto: we can pass a Set<Powerup> or a Set<Player> as a Set<? extends
GameObject> and we can pull a GameObject from a Set<? extends
GameObject>, which is all this method needs to do.
Of course, there's a nice example for <? super Foo> as well. It's when
we are inputting objects of a generic type but not retrieving them.
Consider a pipeline of some kind in which objects are put into a
Pipeline object and Pipelines notify their Listeners with these objects,
allowing loose coupling and runtime rearrangement of the plumbing. This
might be for process coordination or whatever. Ultimately, we need a
Pipeline<T> to accept Ts and pass them to Listener<T>s. We also want to
be able to join pipelines together so that many run together; if we do a
pipelineA.mergeInto(pipelineB) we want any object sent to pipeline A to
get sent to B as well, and on to anything B does in turn. We come up with:
public interface Listener<T> {
public void handleObject(T object);
}
public class Pipeline<T> {
private List<Listener<T>> listeners
= new List<Listener<T>>();
public void handleObject(T object) {
for (Listener<T> listener : listeners) {
listener.handleObject(object);
}
}
public void addListener (Listener<T> listener) {
listeners.add(listener);
}
public void mergeInto (Pipeline<T> p) {
// hmm ... how to do this?
}
}
Then the brilliant idea strikes: just add "implements Listener<T>" to
the class definition. Pipeline already has a handleObject(T) method, and
that method does exactly what's needed to pass the object on to its own
listeners, so adding a Pipeline as a listener to another Pipeline works
beautifully. And mergeInto just has to behave like addListener(p)! In
fact, the method could go away entirely, save that it helps callers
write self-documenting code that makes it clear that major plumbing is
merging and not just being tapped at that point.
Unfortunately, we run into a problem when we go to merge a
pipeline<Integer> to a pipeline<Object>. Logically we should be able to,
since any listener that can cope with an Object can cope with an
Integer, and a flow of Integers dumping into a flow of Objects isn't
violating any type constraints, surely?
It's "super" to the rescue:
public class Pipeline<T> implements Listener<T> {
private List<Listener<? super T>> listeners
= new List<Listener<? super T>>();
public void handleObject(T object) {
for (Listener<? super T> listener : listeners) {
listener.handleObject(object);
// Fine -- object is a T so it can safely
// be cast to any ? super T
}
}
public void addListener (Listener<? super T> listener) {
listeners.add(listener);
}
public void mergeInto (Pipeline<? super T> p) {
listeners.add(p);
}
}
This also lets us register a generic Object listener with an Integer
pipeline, as well as merging pipelines of Integers, Strings, and
whatever else into a pipeline of Objects.