考虑编写一种方法,该方法采用一个对象数组和一个集合,并将该数组中的所有对象放入集合中。这是第一次尝试:

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

    到现在为止,您已经学会了避免初学者尝试将Collection<Object>用作collection参数类型的错误。您可能会,也可能不会认识到,使用Collection<?>也不起作用。回想一下,您不能仅将对象推入未知类型的集合中。
    解决这些问题的方法是使用泛型方法(generic methods。就像类型声明一样,方法声明可以是泛型的,即由一个或多个类型参数进行参数化。

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

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

    1. Object[] oa = new Object[100];
    2. Collection<Object> co = new ArrayList<Object>();
    3. // T inferred to be Object
    4. fromArrayToCollection(oa, co);
    5. String[] sa = new String[100];
    6. Collection<String> cs = new ArrayList<String>();
    7. // T inferred to be String
    8. fromArrayToCollection(sa, cs);
    9. // T inferred to be Object
    10. fromArrayToCollection(sa, co);
    11. Integer[] ia = new Integer[100];
    12. Float[] fa = new Float[100];
    13. Number[] na = new Number[100];
    14. Collection<Number> cn = new ArrayList<Number>();
    15. // T inferred to be Number
    16. fromArrayToCollection(ia, cn);
    17. // T inferred to be Number
    18. fromArrayToCollection(fa, cn);
    19. // T inferred to be Number
    20. fromArrayToCollection(na, cn);
    21. // T inferred to be Object
    22. fromArrayToCollection(na, co);
    23. // compile-time error
    24. fromArrayToCollection(na, cs);

    注意,我们不必将实际的类型参数传递给泛型方法。编译器根据实际参数的类型为我们推断出类型参数。通常,它将推断出将使调用类型正确的最具体的类型参数。
    出现的一个问题是:什么时候应该使用泛型方法,什么时候应该使用通配符类型?为了理解答案,让我们研究一下Collection库中的一些方法。

    1. interface Collection<E> {
    2. public boolean containsAll(Collection<?> c);
    3. public boolean addAll(Collection<? extends E> c);
    4. }


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

    1. interface Collection<E> {
    2. public <T> boolean containsAll(Collection<T> c);
    3. public <T extends E> boolean addAll(Collection<T> c);
    4. // Hey, type variables can have bounds too!
    5. }

    但是,在containsAlladdAll中,类型参数T仅使用一次。返回类型不依赖于类型参数,也不依赖于该方法的任何其他参数(在这种情况下,仅存在一个参数)。这告诉我们类型参数用于多态。它的唯一作用是允许在不同的调用站点使用各种实际的参数类型。在这种情况下,应使用通配符。通配符旨在支持灵活的子类型化,这就是我们在此要表达的内容。
    泛型方法允许使用类型参数来表示方法的一个或多个参数的类型和/或其返回类型之间的依赖性。如果没有这种依赖性,则不应使用泛型方法。
    可以同时使用泛型方法和通配符。这是方法Collections.copy()

    1. class Collections {
    2. public static <T> void copy(List<T> dst, List<? extends T> src) {
    3. ...
    4. }

    注意两个参数的类型之间的依赖关系。从源列表src复制的任何对象都必须可分配给目标列表dst的元素类型T。因此,src的元素类型可以是T的任何子类型——我们不在乎。copy的签名使用类型参数表示依赖性,但对第二个参数的元素类型使用通配符。
    我们可以以另一种方式编写此方法的签名,而根本不使用通配符:

    1. class Collections {
    2. public static <T, S extends T> void copy(List<T> dst, List<S> src) {
    3. ...
    4. }

    很好,虽然在第二类型参数的类型dst和边界中都使用了第一类型参数S,但S本身仅被使用一次,而第二类型参数src的类型中没有其他依赖。这表明我们可以用通配符代替S。使用通配符比声明显式类型参数更清晰,更简洁,因此应尽可能使用通配符。
    通配符还具有可以在方法签名之外用作字段,局部变量和数组的类型的优点。这是一个例子。
    回到我们的形状绘制问题,假设我们要保留绘制请求的历史记录。我们可以将历史记录保存在Shape类内的静态变量中,drawAll()将其传入参数存储到history字段中。

    1. static List<List<? extends Shape>>
    2. history = new ArrayList<List<? extends Shape>>();
    3. public void drawAll(List<? extends Shape> shapes) {
    4. history.addLast(shapes);
    5. for (Shape s: shapes) {
    6. s.draw(this);
    7. }
    8. }

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