# 通用方法

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

考虑编写一个方法，该方法接受一组对象和一个集合，并将数组中的所有对象放入集合中。这是第一次尝试：

```
static void fromArrayToCollection(Object[] a, Collection<?> c) {
    for (Object o : a) { 
        c.add(o); // compile-time error
    }
}

```

到目前为止，您将学会避免初学者错误地尝试使用`Collection&lt;Object&gt;`作为集合参数的类型。您可能已经或可能没有认识到使用`Collection&lt;?&gt;`也无法正常工作。回想一下，您不能只将对象推送到未知类型的集合中。

处理这些问题的方法是使用 _ 泛型方法 _。就像类型声明一样，方法声明可以是通用的 - 也就是说，由一个或多个类型参数参数化。

```
static <T> void fromArrayToCollection(T[] a, Collection<T> c) {
    for (T o : a) {
        c.add(o); // Correct
    }
}

```

我们可以使用任何类型的集合调用此方法，其元素类型是数组元素类型的超类型。

```
Object[] oa = new Object[100];
Collection<Object> co = new ArrayList<Object>();

// T inferred to be Object
fromArrayToCollection(oa, co); 

String[] sa = new String[100];
Collection<String> cs = new ArrayList<String>();

// T inferred to be String
fromArrayToCollection(sa, cs);

// T inferred to be Object
fromArrayToCollection(sa, co);

Integer[] ia = new Integer[100];
Float[] fa = new Float[100];
Number[] na = new Number[100];
Collection<Number> cn = new ArrayList<Number>();

// T inferred to be Number
fromArrayToCollection(ia, cn);

// T inferred to be Number
fromArrayToCollection(fa, cn);

// T inferred to be Number
fromArrayToCollection(na, cn);

// T inferred to be Object
fromArrayToCollection(na, co);

// compile-time error
fromArrayToCollection(na, cs);

```

请注意，我们不必将实际类型参数传递给泛型方法。编译器根据实际参数的类型为我们推断类型参数。它通常会推断出使调用类型更正的最具体的类型参数。

出现的一个问题是：何时应该使用泛型方法，何时应该使用通配符类型？为了理解答案，我们来看一下`Collection`库中的一些方法。

```
interface Collection<E> {
    public boolean containsAll(Collection<?> c);
    public boolean addAll(Collection<? extends E> c);
}

```

我们可以在这里使用泛型方法：

```
interface Collection<E> {
    public <T> boolean containsAll(Collection<T> c);
    public <T extends E> boolean addAll(Collection<T> c);
    // Hey, type variables can have bounds too!
}

```

但是，在`containsAll`和`addAll`中，类型参数`T`仅使用一次。返回类型不依赖于类型参数，也不依赖于方法的任何其他参数（在这种情况下，只有一个参数）。这告诉我们类型参数用于多态;它唯一的作用是允许在不同的调用站点使用各种实际的参数类型。如果是这种情况，则应使用通配符。通配符旨在支持灵活的子类型，这是我们在此尝试表达的内容。

通用方法允许使用类型参数来表示方法和/或其返回类型的一个或多个参数的类型之间的依赖关系。如果没有这种依赖关系，则不应使用通用方法。

可以串联使用通用方法和通配符。这是方法`Collections.copy()`：

```
class Collections {
    public static <T> void copy(List<T> dest, List<? extends T> src) {
    ...
}

```

注意两个参数类型之间的依赖关系。从源列表`src`复制的任何对象必须可分配给目标列表的元素类型`T`，`dst`。所以`src`的元素类型可以是`T`的任何子类型 - 我们不关心哪个。 `copy`的签名使用类型参数表示依赖关系，但对第二个参数的元素类型使用通配符。

我们可以用另一种方式为这种方法编写签名，而不使用通配符：

```
class Collections {
    public static <T, S extends T> void copy(List<T> dest, List<S> src) {
    ...
}

```

这很好，但是第一个类型参数既用于`dst`类型又用于第二个类型参数的边界，`S`，`S`本身仅使用一次，类型为`src` - 没有别的东西取决于它。这表明我们可以用通配符替换`S`。使用通配符比声明显式类型参数更清晰，更简洁，因此应尽可能优先使用通配符。

通配符还具有以下优点：它们可以在方法签名之外使用，如字段类型，局部变量和数组。这是一个例子。

回到我们的形状绘制问题，假设我们想要保留绘图请求的历史记录。我们可以在类`Shape`内的静态变量中维护历史记录，并让`drawAll()`将其传入的参数存储到历史记录字段中。

```
static List<List<? extends Shape>> 
    history = new ArrayList<List<? extends Shape>>();

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

```

最后，再次让我们注意用于类型参数的命名约定。我们使用`T`作为类型，只要没有更具体的类型来区分它。通用方法通常就是这种情况。如果有多个类型参数，我们可能会使用字母表中与`T`相邻的字母，例如`S`。如果泛型方法出现在泛型类中，最好避免对方法和类的类型参数使用相同的名称，以避免混淆。这同样适用于嵌套泛型类。