[译] 泛型 9.通配符的更多用法

<< 类字面量作为运行时类型标记 · 目录 · 将老式代码转换为使用泛型 >>

                                                                                  

通配符的更多用法 (More Fun with Wildcards)

在本节中,我们将考虑通配符的一些更高级的用途。我们已经看到了几个例子,其中有界通配符在从数据结构读取数据时非常有用。现在考虑相反的情况,即只写数据结构。接口Sink就是这样一个简单的例子:

interface Sink<T> {
    flush(T t);
}

我们可以像下面的代码所演示的那样使用它。方法writeAll()的目的是将集合coll的所有元素刷新到接收器snk,并返回最后一个已刷新的元素。

public static <T> T writeAll(Collection<T> coll, Sink<T> snk) {
    T last;
    for (T t : coll) {
        last = t;
        snk.flush(last);
    }
    return last;
}
...
Sink<Object> s;
Collection<String> cs;
String str = writeAll(cs, s); // 非法调用

正如所写的,对writeAll()的调用是非法的,因为编译器不能推断有效的类型参数;String和Object都不是T的适当类型,因为Collection的元素和Sink的元素必须是相同的类型。

我们可以通过修改writeAll()的签名来修复这个错误,如下所示,使用通配符:

public static <T> T writeAll(Collection<? extends T>, Sink<T>) {...}
...
// 调用没有问题,但是返回类型错误 
String str = writeAll(cs, s);

调用现在是合法的,但是赋值是错误的,因为推断的返回类型是Object,因为T匹配s的元素类型,即Object。

解决方案是使用一种我们尚未见过的有界通配符形式:有下界的通配符。语法 ? Super T 表示未知类型?是T的一个超类型(或T本身;请记住,超类型关系是自反的)。它是我们一直使用的有界通配符的对偶,使用 ? extends T 来表示未知类型?是T的一个子类型。

public static <T> T writeAll(Collection<T> coll, Sink<? super T> snk) {
    ...
}
String str = writeAll(cs, s); // Yes! 

使用此语法,调用是合法的,推断类型为String,这正是所需的。

现在让我们转到一个更实际的例子。java.util.TreeSet<E>表示有序的E类型元素的树。构造TreeSet的一种方法是将Comparator对象传递给构造函数。该比较器将用于根据所需的顺序对TreeSet的元素进行排序。

TreeSet(Comparator<E> c) 

比较器接口大体如下:

interface Comparator<T> {
    int compare(T fst, T snd);
}

假设我们想要创建一个TreeSet<String>并传入一个合适的比较器,我们需要传递一个Comparator来比较字符串。这可以由Comparator<string>完成,但是Comparator<Object>也可以。但是,我们将无法调用上面给出的Comparator<Object>上的构造函数。我们可以使用一个下界的有界通配符来获得我们想要的灵活性:

TreeSet(Comparator<? super E> c) 

此代码允许使用任何适用的比较器。

使用下界通配符的最后一个例子,让我们看一看方法Collections.max(),它返回作为参数传递给它的集合中的最大元素。现在,为了让max()工作,传入的集合的所有元素都必须实现Comparable。此外,它们必须是可以相互比较的。

第一次尝试使用泛型方法的签名:

public static <T extends Comparable<T>> T max(Collection<T> coll)

也就是说,该方法接受一个可与其自身相比较的类型T的集合,并返回该类型的元素。但是,这段代码的限制性太强了。请考虑下与任意对象相比较的类型:

class Foo implements Comparable<Object> {
    ...
}
Collection<Foo> cf = ... ;
Collections.max(cf); // Should work.

cf的每个元素都可以与cf中的其他元素进行比较,因为每个这样的元素都是Foo,它可以与任何对象,特别是另一个Foo进行比较。然而,使用上面的签名,我们发现调用被拒绝。推断的类型必须是Foo,但是Foo没有实现Comparable<Foo>。

它没有必要和它本身相比,所需要的就是T能与它的某个超类型相比较。这样:

public static <T extends Comparable<? super T>> T max(Collection<T> coll)

注意,Collections.max()的实际签名更加复杂。在下一节将老式代码转换为使用泛型中,我们将再次见到它。这种推理几乎适用于适合任意类型的Comparable用法:您总是希望使用Comparable<? super T>。

通常,如果您的API只使用类型参数T作为参数,那么它的使用应该利用下界通配符(? super T)。相反,如果API只返回T,则通过使用上限通配符(? extends T)。

通配符捕获 (wildcard capture)

现在应该很清楚了,如下:

Set<?> unknownSet = new HashSet<String>();
...
/* 添加一个元素 t 到一个 Set s. */ 
public static <T> void addToSet(Set<T> s, T t) {
    ...
}

下面是非法调用:

addToSet(unknownSet, "abc"); // Illegal.

传递的实际集合是一组字符串,这一点没有区别;重要的是,参数传递的是一组未知类型的集合,不能保证它是一组字符串,特别是任意类型。

现在,考虑以下代码:

class Collections {
    ...
    <T> public static Set<T> unmodifiableSet(Set<T> set) {
        ...
    }
}
...
Set<?> s = Collections.unmodifiableSet(unknownSet); // 可以正常运行?为什么?

这似乎是不允许的;然而,看看这个特定的调用,允许它是完全安全的。毕竟,unedfiableSet()确实适用于任何类型的集合,而不管它的元素类型如何。

由于这种情况出现的相对频繁,因此有一个特殊的规则,允许在可以证明代码是安全的特定环境下使用这种代码。这个规则称为通配符捕获,允许编译器将未知类型的通配符推断为泛型方法的类型参数。

                                                                                  

<< 类字面量作为运行时类型标记 · 目录 · 将老式代码转换为使用泛型 >>

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值