void printCollection(Collection c) { Iterator i = c.iterator(); for (k = 0; k < c.size(); k++) { System.out.println(i.next()); } }
for
loop syntax):
void printCollection(Collection<Object> c) { for (Object e : c) { System.out.println(e); } }
Collection<Object>
, which,
as we've just demonstrated, is not a supertype of all kinds of
collections!
So what is the supertype of all kinds of collections? It's written
Collection<?>
(pronounced "collection of unknown"),
that is, a collection whose element type matches
anything. It's called a wildcard type for obvious reasons. We can
write:
void printCollection(Collection<?> c) { for (Object e : c) { System.out.println(e); } }
printCollection()
, we can still read elements
from c
and give them type Object
. This is always safe,
since whatever the actual type of the collection, it does contain
objects. It isn't safe to add arbitrary objects to it however:
Collection<?> c = new ArrayList<String>(); c.add(new Object()); // Compile time error
c
stands for,
we cannot add objects to it. The add()
method takes arguments
of type E
, the element type of the collection. When the actual
type parameter is ?
, it stands for some unknown type. Any parameter
we pass to add
would have to be a subtype of this unknown type.
Since we don't know what type that is, we cannot pass anything in. The sole
exception is null
, which is a member of every type.
On the other hand, given a List<?>
, we can call
get()
and make use of the result. The result type is an
unknown type, but we always
know that it is an object. It is therefore safe to assign the result of
get()
to a variable of type Object
or pass it as a parameter
where the type Object
is expected.
public abstract class Shape { public abstract void draw(Canvas c); } public class Circle extends Shape { private int x, y, radius; public void draw(Canvas c) { ... } } public class Rectangle extends Shape { private int x, y, width, height; public void draw(Canvas c) { ... } }
public class Canvas { public void draw(Shape s) { s.draw(this); } }
Canvas
that draws them all:
public void drawAll(List<Shape> shapes) { for (Shape s: shapes) { s.draw(this); } }
drawAll()
can only be called on
lists of exactly Shape
: it cannot, for instance, be called on a
List<Circle>
. That is unfortunate, since all the method does
is read shapes from the list, so it could just as well be called on a
List<Circle>
. What we really want is for the method to accept
a list of any kind of shape:
public void drawAll(List<? extends Shape> shapes) { ... }
List<Shape>
with List<? extends Shape>
. Now
drawAll()
will accept lists of any subclass of Shape
, so
we can now call it on a List<Circle>
if we want.
List<? extends Shape>
is an example of a bounded wildcard.
The ?
stands for an unknown type, just like the wildcards we saw
earlier. However, in this case, we know that this unknown type is in fact a subtype
of Shape
. (Note: It could be Shape
itself,
or some subclass; it need not literally extend Shape
.)
We say that Shape
is the
upper bound of the wildcard.
There is, as usual, a price to be paid for the flexibility of using
wildcards. That price is
that it is now illegal to write into shapes
in the body of the
method. For instance, this is not allowed:
public void addRectangle(List<? extends Shape> shapes) { shapes.add(0, new Rectangle()); // Compile-time error! }
shapes.add()
is ? extends Shape
--
an unknown subtype of Shape
. Since we don't know what type it is,
we don't know if it is a supertype of Rectangle
; it might or might not
be such a supertype, so it isn't safe to pass a Rectangle
there.
Bounded wildcards are just what one needs to handle the example
of the DMV passing its data to the census bureau. Our example assumes
that the data is represented by mapping from names (represented as strings)
to people (represented by reference types such as Person
or its
subtypes, such as Driver
). Map<K,V>
is
an example of a generic type that takes two type arguments, representing
the keys and values of the map.
Again, note the naming convention for formal type parameters--K
for keys and V
for values.
public class Census { public static void addRegistry(Map<String, ? extends Person> registry) { } ... Map<String, Driver> allDrivers = ... ; Census.addRegistry(allDrivers);