Documentation

The Java™ Tutorials
Hide TOC
Wildcards通配符
Trail: Bonus
Lesson: Generics

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):以下是您如何使用该语言的旧版本(即5.0之前的版本)编写它:

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):下面是一个使用泛型(以及新的for循环语法)编写它的天真尝试:

void printCollection(Collection<Object> 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<Object>, which, as we've just demonstrated, is not a supertype of all kinds of collections!旧代码可以用任何类型的集合作为参数调用,而新代码只接受Collection<Object>,正如我们刚才演示的,它不是所有类型集合的超类型!

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. 它被写为Collection<?>(发音为“collection of unknown”),即元素类型与任何内容匹配的集合。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. 请注意,在printCollection()中,我们仍然可以从c中读取元素,并将其指定为类型ObjectThis 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

Since we don't know what the element type of c stands for, we cannot add objects to it. 因为我们不知道c的元素类型代表什么,所以我们不能向它添加对象。The add() method takes arguments of type E, the element type of the collection. add()方法接受集合的元素类型E类型的参数。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. 我们传递给add的任何参数都必须是此未知类型的子类型。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.唯一的例外是null,它是每种类型的成员。

On the other hand, given a List<?>, we can call get() and make use of the result. 另一方面,给定一个List<?>,我们可以调用get()并利用结果。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.因此,可以安全地将get()的结果分配给Object类型的变量,或者将其作为参数传递给预期的Object类型的变量。

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:这些类可以在画布上绘制:

public class Canvas {
public void draw(Shape s) {
        s.draw(this);
   }
}

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:假设它们被表示为一个列表,在Canvas中有一个方法可以方便地将它们全部绘制出来:

public void drawAll(List<Shape> 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<Circle>. 现在,类型规则规定drawAll()只能在Shape完全相同的列表上调用:例如,它不能在列表<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>. 这是不幸的,因为该方法所做的只是从列表中读取形状,所以它也可以在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) {
    ...
}

There is a small but very important difference here: we have replaced the type List<Shape> with List<? extends Shape>. 这里有一个很小但非常重要的区别:我们用List<? extends Shape>替换了类型List<Shape>Now drawAll() will accept lists of any subclass of Shape, so we can now call it on a List<Circle> if we want.现在drawAll()将接受Shape的任何子类的列表,因此我们现在可以在列表<Circle>中调用它。

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. 然而,在这种情况下,我们知道这种未知类型实际上是Shape的一个子类型。(Note: It could be Shape itself, or some subclass; it need not literally extend Shape.) (注意:它可以是Shape本身,也可以是某个子类;它不需要逐字扩展Shape。)We say that Shape is the upper bound of the wildcard.我们说Shape是通配符的上界。

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. 这个代价是,现在在方法体中写入shapes是非法的。For instance, this is not allowed:例如,这是不允许的:

public void addRectangle(List<? extends 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. shapes.add()的第二个参数的类型是? extends Shape,它是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.因为我们不知道它是什么类型,所以我们不知道它是否是Rectangle的超类型;它可能是也可能不是这样的超类型,所以在那里传递一个Rectangle是不安全的。

Bounded wildcards are just what one needs to handle the example of the DMV passing its data to the census bureau. 有界通配符正是处理DMV将数据传递给人口普查局的示例所需要的。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). 示例假设数据是通过从名称(以字符串表示)映射到人(以引用类型(如Person)或其子类型(如Driver)表示)来表示的。Map<K,V> is an example of a generic type that takes two type arguments, representing the keys and values of the map.Map<K,V>是一个泛型类型的示例,它接受两个类型参数,表示映射的键和值。

Again, note the naming convention for formal type parameters--K for keys and V for values.再次注意形式类型参数的命名约定——K表示键,V表示值。

public class Census {
public static void addRegistry(Map<String, ? extends Person> registry) {
}
...

Map<String, Driver> allDrivers = ... ;
Census.addRegistry(allDrivers);

Previous page: Generics and Subtyping
Next page: Generic Methods