In my last column, I talked about some of the more subtle aspects of the current generics implementation for Java. In particular, I talked about erasure and bridging. Both erasure and bridging are implementation techniques that the designers of the generic specification adopted for backwards compatibility.
In this column, I'm going to continue talking about advanced aspects of the generics specification. But I'm going to leave the implementation details behind and instead focus on wildcards. Wildcards are a recent addition to the generics specification based on the idea that sometimes you don't want to precisely specify the value for a type parameter. Instead, you want to leave the type parameter unbound, as a signal to the compiler that the exact type isn't important (the important thing is that there is a type parameter, not the particular binding). The name wildcards comes from the fact that what we're talking about is replacing either a named type parameter, such as T, or an actual type with the unbound (and therefore unreferenceable) type parameter ? (the ? is the "wildcard").
Before we start, I'd like to add the usual caveat: JDK 1.5 isn't in its final form yet. It's entirely possible that wildcards won't make it into JDK 1.5, or will make it into JDK 1.5 in a slightly different form.
Acknowledgements: Tom Hill and Martin O'Connor did yeomanlike work, slogging through the early drafts of this article and providing feedback. It's almost certainly still got a few mistakes, but they did a fine job at winnowing the number down.
The Syntax of Wildcards
The board game Othello used to have a slogan: "A minute to learn, a lifetime to master." Similarly, wildcards are a fairly small change to the syntax of generics (a minute to learn), yet are one of the more subtle ideas in the generics specification (a lifetime, or at least an entire article, to master). So rather than dive into the mentally challenging parts of the specification right away, we're going to start slowly and begin with the syntax for wildcards. Once you're familiar with the syntax, we'll move on to talking about the reasons wildcards were added to the specification.
Because the syntax for wildcards is so simple, we're not going to bother with a formal specification. Instead, I'll give you a quick rule, and then proceed using examples. The general rule is that anywhere you could use a type parameter in a field, method, or variable declaration, you can instead use a ?, providing that you don't actually reference the (previously) named type parameter in your code.
Thus, for example, suppose you had the following method:
// Version 1.
public <T extends Animal> void printAnimals(Collection <T> animals) {
for (Animal nextAnimal: animals) {
System.out.println(nextAnimal);
}
}
Note: I'm using the new for loop that's coming in JDK 1.5. See Joshua Bloch's comments for more details.
The body of the method doesn't reference T at all -- the type parameter is declared but not used. In this case, you can remove the declaration of T and can replace the use of T with a "wildcard" (which is always denoted by ?). When we rewrite the method, we get the following code:
// Version 2. Version 1, wildcarded.
public void printAnimals(Collection <? extends Animal> animals) {
for (Animal nextAnimal: animals) {
System.out.println(nextAnimal);
}
}
It's very important that the type parameter not be referenced in the method body. The ? is supposed to drive an intuition that a wide variety of types "match" the declaration (in much the same way that ? is used in regular expressions). If you start trying to use the type of the ? in the body of your code, you're constructing an implicit binding for it (and violating the idea of a wildcard). What's more, your code won't even compile. Consider, for example, the following method, which explicitly references T.
// Version 3. We use the Type Parameter in the method body.
public <T extends Animal> void printAnimals(Collection<T> animals) {
for (T nextAnimal: animals) {
System.out.println(nextAnimal);
}
}
Replacing T by ? gives you:
// Version 4. The ? confuses the compiler
public void printAnimals(Collection<? extends Animal> animals) {
for (? nextAnimal: animals) {
System.out.println(nextAnimal);
}
}
This code doesn't even compile. The compiler reports an "illegal start of expression" error because it's trying to figure out the ternary operator inside the for loop.
Note: if you try to compile Version 3 with the current download, you'll run into a bug in the generics compiler. But if you use an iterator instead of the new for loop, you'll see what I mean.
Similarly, you can use wildcards in field declarations. For example, in the following code snippet, we have a private collection defined using the type parameter T.
public class Trainer<T extends Animal> {
private Collection<T> _myAnimals = new ArrayList<T>();
If the fact that the collection contained instances of T wasn't important, we could have declared the collection as follows:
private Collection<? extends Animal> _myAnimals = new ArrayList<T>();
If you understood these examples, you understand the syntax of wildcards. The only other syntactic extension I want to mention in this article is the "super" keyword. Just as you can declare ? extends Animal, you can declare ? super Animal. For example, the following declaration defines a field whose value must be an instance of Collection whose type parameter is a superclass of Animal.
private Collection<? super Animal> _myCollection;
An instance of Collection<Object> would match this declaration; an instance of Collection<Dog> would not. While we won't cover super in this article (I might make a blog entry about it at some point), it does turn out to be useful in several scenarios and is worth mentioning briefly.
The First Benefit of Wildcards: Cleaner Code
Now that you understand the syntax, you might be wondering, "What's the point?
Why would you want to have an unbound type parameter? When would you use it? Does this really add anything of value to the language?"
Well, the major benefit (below) requires a bit of a deep dive into the type system. But even before that, it's worth noting that wildcards do make code cleaner and easier to read. In a very real sense, the use of ? encapsulates intent, and makes the meaning and purpose of methods clearer. I much prefer
public void printAnimals(Collection <? extends Animal> animals) {
which tells me that this is a general purpose method which doesn't really use any of the properties of Animal once the type has been verified, to
public <T extends Animal> void printAnimals(Collection <T> animals) {
which implies that maybe, somewhere in the method body, T is actually used (and I need to read the method to find out where).