# 通配符

> 原文： [https://docs.oracle.com/javase/tutorial/extra/generics/wildcards.html](https://docs.oracle.com/javase/tutorial/extra/generics/wildcards.html)

考虑编写打印出集合中所有元素的例程的问题。以下是您可以使用旧版本的语言（即 5.0 之前的版本）编写它的方法：

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

```

以下是使用泛型（以及新的`for`循环语法）编写它的天真尝试：

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

```

问题是这个新版本比旧版本更有用。虽然可以使用任何类型的集合作为参数调用旧代码，但新代码只需要`Collection&lt;Object&gt;`，正如我们刚才演示的那样，**而不是**是各种集合的超类型！

那么是什么类型的收藏品的超类型？它写成`Collection&lt;?&gt;`（发音为“未知的集合”），即元素类型与任何东西匹配的集合。由于显而易见的原因，它被称为**通配符类型**。我们可以写：

```
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`的任何参数都必须是这种未知类型的子类型。由于我们不知道它是什么类型，我们无法传递任何内容。唯一的例外是`null`，它是每种类型的成员。

另一方面，给定`List&lt;?&gt;`，我们**可以**调用`get()`并使用结果。结果类型是未知类型，但我们始终知道它是一个对象。因此，可以安全地将`get()`的结果分配给`Object`类型的变量，或将其作为预期类型为`Object`的参数传递。

## 有界通配符

考虑一个简单的绘图应用程序，可以绘制矩形和圆形等形状。要在程序中表示这些形状，您可以定义类层次结构，例如：

```
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&lt;Circle&gt;`上调用。这是不幸的，因为所有方法都是从列表中读取形状，所以它也可以在`List&lt;Circle&gt;`上调用。我们真正想要的是接受**任何**形状列表的方法：

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

```

这里有一个很小但很重要的区别：我们用 `List&lt;？替换了`List&lt;Shape&gt;`类型**扩展** Shape&gt;` 。现在`drawAll()`将接受`Shape`的任何子类的列表，所以我们现在可以根据需要在`List&lt;Circle&gt;`上调用它。

`列表&lt;？ **扩展** Shape&gt;` 是 _ 有界通配符 _ 的一个例子。 `?`代表一种未知类型，就像我们之前看到的通配符一样。但是，在这种情况下，我们知道这种未知类型实际上是`Shape`的子类型。 （注意：它可能是`Shape`本身，或某些子类;它不需要字面上扩展`Shape`。）我们说`Shape`是通配符的 _ 上限 _。

像往常一样，为使用通配符的灵活性付出了代价。这个价格是在方法体中写入`shapes`现在是非法的。例如，这是不允许的：

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

```

您应该能够弄清楚为什么不允许上面的代码。 `shapes.add()`的第二个参数的类型是`？ **延伸** Shape` - `Shape`的未知亚型。由于我们不知道它是什么类型，我们不知道它是否是`Rectangle`的超类型;它可能是也可能不是这样的超类型，所以在那里传递`Rectangle`是不安全的。

有界通配符正是人们需要处理 DMV 将其数据传递给人口普查局的例子。我们的示例假设数据通过从名称（表示为字符串）到人（由`Person`或其子类型等引用类型表示，例如`Driver`）的映射来表示。 `Map&lt;K,V&gt;`是一个泛型类型的示例，它采用两个类型参数，表示映射的键和值。

再次，请注意正式类型参数的命名约定 - 键的`K`和值的`V`。

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

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

```