Wildcards
Consider the problem of writing a routine that prints out all the elements in a collection. Here’s how you might write it in an older version of the language (that is, a pre-5.0 release):
void printCollection(Collection c) < Iterator i = c.iterator(); for (k = 0; k < c.size(); k++) < System.out.println(i.next()); >>
And here is a naive attempt at writing it using generics (and the new for loop syntax):
void printCollection(Collection c) < for (Object e : c) < System.out.println(e); >>
The problem is that this new version is much less useful than the old one. Whereas the old code could be called with any kind of collection as a parameter, the new code only takes Collection , 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); >>
and now, we can call it with any type of collection. Notice that inside 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(); c.add(new Object()); // Compile time error
Since we don’t know what the element type of 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.
Bounded Wildcards
Consider a simple drawing application that can draw shapes such as rectangles and circles. To represent these shapes within the program, you could define a class hierarchy such as this:
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) < . >>
These classes can be drawn on a canvas:
Any drawing will typically contain a number of shapes. Assuming that they are represented as a list, it would be convenient to have a method in Canvas that draws them all:
public void drawAll(List shapes) < for (Shape s: shapes) < s.draw(this); > >
Now, the type rules say that drawAll() can only be called on lists of exactly Shape : it cannot, for instance, be called on a List . 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 . What we really want is for the method to accept a list of any kind of shape:
public void drawAll(Listextends Shape> shapes)
There is a small but very important difference here: we have replaced the type List with Listextends Shape> . Now drawAll() will accept lists of any subclass of Shape , so we can now call it on a List if we want.
Listextends 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(Listextends Shape> shapes) < // Compile-time error! shapes.add(0, new Rectangle()); >
You should be able to figure out why the code above is disallowed. The type of the second parameter to 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 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(Mapextends Person> registry) < >. Map allDrivers = . ; Census.addRegistry(allDrivers);
Guidelines for Wildcard Use
One of the more confusing aspects when learning to program with generics is determining when to use an upper bounded wildcard and when to use a lower bounded wildcard. This page provides some guidelines to follow when designing your code.
For purposes of this discussion, it is helpful to think of variables as providing one of two functions:
An «In» Variable An «in» variable serves up data to the code. Imagine a copy method with two arguments: copy(src, dest). The src argument provides the data to be copied, so it is the «in» parameter. An «Out» Variable An «out» variable holds data for use elsewhere. In the copy example, copy(src, dest), the dest argument accepts data, so it is the «out» parameter.
Of course, some variables are used both for «in» and «out» purposes — this scenario is also addressed in the guidelines.
You can use the «in» and «out» principle when deciding whether to use a wildcard and what type of wildcard is appropriate. The following list provides the guidelines to follow:
- An «in» variable is defined with an upper bounded wildcard, using the extends keyword.
- An «out» variable is defined with a lower bounded wildcard, using the super keyword.
- In the case where the «in» variable can be accessed using methods defined in the Object class, use an unbounded wildcard.
- In the case where the code needs to access the variable as both an «in» and an «out» variable, do not use a wildcard.
These guidelines do not apply to a method’s return type. Using a wildcard as a return type should be avoided because it forces programmers using the code to deal with wildcards.
A list defined by List can be informally thought of as read-only, but that is not a strict guarantee. Suppose you have the following two classes:
class NaturalNumber < private int i; public NaturalNumber(int i) < this.i = i; >// . > class EvenNumber extends NaturalNumber < public EvenNumber(int i) < super(i); >// . >
Consider the following code:
List le = new ArrayList<>(); List extends NaturalNumber>ln = le; ln.add(new NaturalNumber(35)); // compile-time error
Because List is a subtype of List , you can assign le to ln. But you cannot use ln to add a natural number to a list of even numbers. The following operations on the list are possible:
- You can add null.
- You can invoke clear.
- You can get the iterator and invoke remove.
- You can capture the wildcard and write elements that you've read from the list.
You can see that the list defined by List is not read-only in the strictest sense of the word, but you might think of it that way because you cannot store a new element or change an existing element in the list.