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

  1. void printCollection(Collection c) {
  2. Iterator i = c.iterator();
  3. for (int k = 0; k < c.size(); k++) {
  4. System.out.println(i.next());
  5. }
  6. }

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

  1. void printCollection(Collection<Object> c) {
  2. for (Object e : c) {
  3. System.out.println(e);
  4. }
  5. }

问题在于,旧版本比新版本有用得多。可以使用任何种类的集合作为参数来调用旧代码,而新代码仅采用Collection<Object>,正如我们刚刚演示的那样,它不是所有种类集合的超类型!
那么,什么所有类型集合的超集合?它是写作Collection<?>(读作“未知集合”),即元素类型与任何内容匹配的集合。由于明显的原因,它被称为通配符类型。我们可以这样写:

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

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

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

由于我们不知道c代表什么元素类型,因此无法向其中添加对象。add()方法接受类型E,即集合的元素类型的参数。当实际类型参数为?时,它代表某种未知类型。我们传递给add的任何参数,都必须是此未知类型的子类型。由于我们不知道是什么类型,因此无法传递任何内容。唯一的例外是null,它是每种类型的成员。
另一方面,给定一个List<?>,我们可以调用get()并使用结果。结果类型是未知类型,但我们始终知道它是一个对象。因此,将结果get()赋给Object类型变量,或将其作为参数传递给Object期望类型是安全的。

有界通配符

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

  1. public abstract class Shape {
  2. public abstract void draw(Canvas c);
  3. }
  4. public class Circle extends Shape {
  5. private int x, y, radius;
  6. public void draw(Canvas c) {
  7. ...
  8. }
  9. }
  10. public class Rectangle extends Shape {
  11. private int x, y, width, height;
  12. public void draw(Canvas c) {
  13. ...
  14. }
  15. }

这些类可以在画布上绘制:

  1. public class Canvas {
  2. public void draw(Shape s) {
  3. s.draw(this);
  4. }
  5. }

任何图形通常都会包含许多形状。假设它们表示为列表,那么在Canvas使用一种方法将它们全部绘制起来将很方便:

  1. public void drawAll(List<Shape> shapes) {
  2. for (Shape s: shapes) {
  3. s.draw(this);
  4. }
  5. }

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

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

这里有一个小,但很重要的区别:我们已使用List<? **extends** Shape>``drawAll()更换类型List<Shape>。现在将接受Shape的任何子类的列表,因此我们现在可以根据需要在List<Circle>上调用它。
List<? **extends** Shape>界通配符(bounded wildcard__)的示例。?代表未知类型,就像我们前面看到的通配符。但是,在这种情况下,我们知道该未知类型实际上是Shape的子类型。(注意:它可以是Shape本身,也可以是某些子类;它不需要从字面上扩展Shape。)我们说Shape是通配符的上界(upper bound
和往常一样,使用通配符的灵活性要付出一定的代价。这个代价是,现在在该方法的主体中写入shapes是非法的。例如,这是不允许的:

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

您应该能够弄清楚为什么不允许上面的代码。shapes.add()第二个参数的类型是Shape的未知子类型? **extends** Shape。由于我们不知道它是什么类型,因此我们不知道它是否是Rectangle的超类型。它可能是也可能不是这样的超类型,因此在那儿传递Rectangle是不安全的。
有界通配符就是处理DMV将其数据传递给人口普查局的示例所需要的。我们的示例假设数据是通过从名称(以字符串表示)到人(由诸如引用类型Person或其子类型,如Driver表示)的映射来表示的。Map<K,V>是具有两个类型参数的通用类型的示例,它们代表映射的键和值。
同样,请注意形式类型参数的命名约定—— K键和V值。

  1. public class Census {
  2. public static void addRegistry(Map<String, ? extends Person> registry) {
  3. }
  4. ...
  5. Map<String, Driver> allDrivers = ... ;
  6. Census.addRegistry(allDrivers);