Documentation

The Java™ Tutorials
Hide TOC
Generic Methods泛型方法
Trail: Bonus
Lesson: Generics

Generic Methods通用方法

Consider writing a method that takes an array of objects and a collection and puts all objects in the array into the collection. 考虑编写一个方法,该方法采用对象数组和集合,并将数组中的所有对象放入集合中。Here's a first attempt:这里是第一次尝试:

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

By now, you will have learned to avoid the beginner's mistake of trying to use Collection<Object> as the type of the collection parameter. 到目前为止,您已经学会了避免初学者尝试使用Collection<Object>作为集合参数类型的错误。You may or may not have recognized that using Collection<?> isn't going to work either. 您可能认识到,使用Collection<?>这也行不通。Recall that you cannot just shove objects into a collection of unknown type.回想一下,您不能只是将对象推入未知类型的集合中。

The way to do deal with these problems is to use generic methods. 解决这些问题的方法是使用泛型方法Just like type declarations, method declarations can be generic—that is, parameterized by one or more type parameters.与类型声明一样,方法声明可以是泛型的,也就是说,由一个或多个类型参数参数化。

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

We can call this method with any kind of collection whose element type is a supertype of the element type of the array.我们可以对任何类型的集合调用这个方法,这些集合的元素类型是数组元素类型的超类型。

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);

Notice that we don't have to pass an actual type argument to a generic method. 请注意,我们不必将实际的类型参数传递给泛型方法。The compiler infers the type argument for us, based on the types of the actual arguments. 编译器根据实际参数的类型为我们推断类型参数。It will generally infer the most specific type argument that will make the call type-correct. 它通常会推断出使调用类型正确的最具体的类型参数。

One question that arises is: when should I use generic methods, and when should I use wildcard types? 出现的一个问题是:什么时候应该使用泛型方法,什么时候应该使用通配符类型?To understand the answer, let's examine a few methods from the Collection libraries.为了理解答案,让我们研究一下Collection库中的一些方法。

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

We could have used generic methods here instead:我们本可以在这里使用通用方法:

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!
}

However, in both containsAll and addAll, the type parameter T is used only once. 然而,在containsAlladdAll中,类型参数T只使用一次。The return type doesn't depend on the type parameter, nor does any other argument to the method (in this case, there simply is only one argument). 返回类型不依赖于类型参数,也不依赖于方法的任何其他参数(在本例中,只有一个参数)。This tells us that the type argument is being used for polymorphism; its only effect is to allow a variety of actual argument types to be used at different invocation sites. 这告诉我们类型参数用于多态性;它唯一的作用是允许在不同的调用站点使用各种实际参数类型。If that is the case, one should use wildcards. 如果是这样,就应该使用通配符。Wildcards are designed to support flexible subtyping, which is what we're trying to express here.通配符被设计为支持灵活的子类型,这就是我们在这里试图表达的。

Generic methods allow type parameters to be used to express dependencies among the types of one or more arguments to a method and/or its return type. 泛型方法允许使用类型参数来表示方法和/或其返回类型的一个或多个参数的类型之间的依赖关系。If there isn't such a dependency, a generic method should not be used.如果没有这种依赖关系,就不应该使用泛型方法。

It is possible to use both generic methods and wildcards in tandem. 可以同时使用泛型方法和通配符。Here is the method Collections.copy():下面是方法Collections.copy()

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

Note the dependency between the types of the two parameters. 请注意这两个参数的类型之间的依赖关系。Any object copied from the source list, src, must be assignable to the element type T of the destination list, dst. 从源列表src复制的任何对象都必须可分配给目标列表dst的元素类型TSo the element type of src can be any subtype of T—we don't care which. 所以src的元素类型可以是T的任何子类型,我们不在乎哪个。The signature of copy expresses the dependency using a type parameter, but uses a wildcard for the element type of the second parameter.copy的签名使用类型参数表示依赖关系,但第二个参数的元素类型使用通配符。

We could have written the signature for this method another way, without using wildcards at all:我们本可以用另一种方式编写此方法的签名,而完全不使用通配符:

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

This is fine, but while the first type parameter is used both in the type of dst and in the bound of the second type parameter, S, S itself is only used once, in the type of src—nothing else depends on it. 这很好,但是,虽然第一个类型参数在dst类型和第二个类型参数的范围内都使用,但SS本身只使用一次,在src类型中,其他任何东西都不依赖于它。This is a sign that we can replace S with a wildcard. 这表明我们可以用通配符替换SUsing wildcards is clearer and more concise than declaring explicit type parameters, and should therefore be preferred whenever possible.使用通配符比声明显式类型参数更清晰、更简洁,因此应尽可能首选通配符。

Wildcards also have the advantage that they can be used outside of method signatures, as the types of fields, local variables and arrays. 通配符还有一个优点,即它们可以在方法签名之外使用,例如字段、局部变量和数组的类型。Here is an example.下面是一个例子。

Returning to our shape drawing problem, suppose we want to keep a history of drawing requests. 回到形状绘制问题,假设我们想要保存绘制请求的历史记录。We can maintain the history in a static variable inside class Shape, and have drawAll() store its incoming argument into the history field.我们可以在类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);
    }
}

Finally, again let's take note of the naming convention used for the type parameters. 最后,让我们再次注意用于类型参数的命名约定。We use T for type, whenever there isn't anything more specific about the type to distinguish it. 我们用T来表示类型,只要类型没有更具体的东西来区分它。This is often the case in generic methods. 这在泛型方法中经常出现。If there are multiple type parameters, we might use letters that neighbor T in the alphabet, such as S. 如果有多个类型参数,我们可以使用字母表中与T相邻的字母,例如SIf a generic method appears inside a generic class, it's a good idea to avoid using the same names for the type parameters of the method and class, to avoid confusion. 如果泛型方法出现在泛型类中,最好避免对方法和类的类型参数使用相同的名称,以避免混淆。The same applies to nested generic classes. 这同样适用于嵌套泛型类。


Previous page: Wildcards
Next page: Interoperating with Legacy Code