[译] 泛型 5.泛型方法

<< 通配符 · 目录 · 与老式代码交互 >>

                                                                                  

泛型方法 (Generic Methods) 

思考写一个方法,该方法接受 对象数组 和 集合 参数,并实现将数组的所有对象放入集合中。第一次尝试这样写:

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

现在,你应该已经学会避免像初学者尝试使用Collection<Object>作为集合参数类型的错误了。你或许已经认识到使用Collection<?>也是行不通的。回想一下——不能将对象推入未知类型的集合中。

解决这些问题的方法是使用泛型方法。就像类型声明一样,方法声明可以是泛型的,也就是说,由一个或多个类型参数进行参数化:

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);
    // 类型变量也可以有界
}

然而,在containsAll和addAll中,类型参数T只使用了一次。返回类型不依赖于类型参数,也不依赖于方法的任何其他参数(在本例中,只有一个参数)。这告诉我们,类型参数用于多态性;它的唯一效果是允许在不同的调用站点上使用各种实际参数类型。如果是这种情况,应该使用通配符。通配符是为了支持灵活的子类型而设计的,这正是我们在这里要强调的。

泛型方法允许使用类型参数来表示方法的一个或多个参数类型或与其返回类型之间的依赖关系。如果不存在这种依赖关系,则不应该使用泛型方法。

可以同时使用泛型方法和通配符。下面是Collectiontions.Copy()方法:

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

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

我们可以用另一种方式编写此方法的签名,不使用通配符:

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

这很好,但是第1个类型参数同时用于dest类型和第2个类型参数S的界中,而S本身只在src类型中使用过一次,没有其他的依赖于S了。这表明我们可以用通配符替换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。如果泛型方法出现在泛型类中,最好避免对方法和类的类型参数使用相同的名称,以避免混淆。这同样适用于嵌套的泛型类。

                                                                                  

<< 通配符 · 目录 · 与老式代码交互 >>

本文译自:https://docs.oracle.com/javase/tutorial/extra/generics/methods.html

转载于:https://my.oschina.net/tita/blog/2886604

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值