Wildcards

考虑编写例程以打印出集合中所有元素的问题。这是您使用较旧版本的语言(即 5.0 之前的版本)编写代码的方法:

void printCollection(Collection c) {
    Iterator i = c.iterator();
    for (k = 0; k < c.size(); k++) {
        System.out.println(i.next());
    }
}

这是天真的try使用泛型(和新的for循环语法)编写它:

void printCollection(Collection<Object> c) {
    for (Object e : c) {
        System.out.println(e);
    }
}

问题在于,这个新版本比旧版本有用得多。可以使用任何类型的集合作为参数来调用旧代码,而新代码仅使用Collection<Object>,正如我们刚刚演示的那样,它不是所有集合的超类型!

那么,各种集合的超类型是什么?它被写为Collection<?>(读作“未知集合”),即元素类型与任何内容匹配的集合。出于明显的原因,它被称为“通配符类型”。我们可以这样写:

void printCollection(Collection<?> c) {
    for (Object e : c) {
        System.out.println(e);
    }
}

现在,我们可以使用任何类型的集合来调用它。请注意,在printCollection()内部,我们仍然可以从c中读取元素,并为其指定类型Object。这始终是安全的,因为无论集合的实际类型如何,它都确实包含对象。但是向其中添加任意对象并不安全:

Collection<?> c = new ArrayList<String>();
c.add(new Object()); // Compile time error

由于我们不知道c的元素类型代表什么,因此无法向其添加对象。 add()方法采用E类型的参数,即集合的元素类型。当实际类型参数为?时,它代表某种未知类型。我们传递给add的任何参数都必须是此未知类型的子类型。由于我们不知道是什么类型,因此无法传递任何内容。唯一的 exception 是null,它是每种类型的成员。

另一方面,给定List<?>,我们可以**调用get()并利用结果。结果类型是未知类型,但我们始终知道它是一个对象。因此,将get()的结果分配给Object类型的变量或将其作为参数传递给Object类型是安全的。

Bounded Wildcards

考虑一个简单的绘图应用程序,它可以绘制矩形和圆形等形状。为了在程序中表示这些形状,您可以定义这样的类层次结构:

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中有一个方法可以将它们全部绘制出来将很方便:

public void drawAll(List<Shape> shapes) {
    for (Shape s: shapes) {
        s.draw(this);
   }
}

现在,类型规则说drawAll()只能在正好Shape的列表上调用:例如,不能在List<Circle>上调用。不幸的是,因为所有方法所做的都是从列表中读取形状,因此也可以在List<Circle>上调用它。我们 true 想要的是该方法可以接受任何类型的形状列表:

public void drawAll(List<? extends Shape> shapes) {
    ...
}

这里有一个很小但非常重要的区别:我们已将List<Shape>类型替换为List<? extends Shape>。现在drawAll()将接受Shape的任何子类的列表,因此我们现在可以根据需要在List<Circle>上调用它。

List<? extends Shape>是有界通配符的示例。 ?代表未知类型,就像我们之前看到的通配符一样。但是,在这种情况下,我们知道该未知类型实际上是Shape的子类型。 (注意:它本身可以是Shape或某些子类;它不必从字面上扩展Shape.)我们说Shape是通配符的上限。

与往常一样,使用通配符的灵 Active 要付出一定的代价。这样做的代价是现在在该方法的主体中写入shapes是非法的。例如,这是不允许的:

public void addRectangle(List<? extends Shape> shapes) {
    // Compile-time error!
    shapes.add(0, new Rectangle());
}

您应该能够弄清楚为什么不允许上面的代码。 shapes.add()的第二个参数的类型是? extends Shape-Shape的未知子类型。因为我们不知道它是什么类型,所以我们不知道它是否是Rectangle的超类型;它可能是也可能不是这样的超类型,因此在其中传递Rectangle是不安全的。

有界通配符只是处理 DMV 将其数据传递给人口普查局的示例所需要的。我们的示例假设数据是通过从名称(以字符串 表示)到人(由诸如Person之类的引用类型或其诸如Driver之类的引用类型表示)的 Map 来表示的。 Map<K,V>是带有两个类型参数的泛型类型的示例,它们表示 Map 的键和值。

同样,请注意形式类型参数的命名约定-K表示键,V表示值。

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

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