通配符带来更多乐趣

在本节中,我们将考虑通配符的一些更高级的用法。我们已经看到了几个示例,其中从数据结构读取数据时,有界通配符很有用。现在考虑相反的只写数据结构。 Sinkinterface是此类的简单示例。

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); // Illegal call.

如所写,对writeAll()的调用是非法的,因为无法推断出有效的 type 参数。 StringObject都不是T的适当类型,因为Collection元素和Sink元素必须是同一类型。

我们可以通过使用通配符如下所示修改writeAll()的签名来解决此错误。

public static <T> T writeAll(Collection<? extends T>, Sink<T>) {...}
...
// Call is OK, but wrong return type. 
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对象传递给构造函数。该比较器将用于根据所需 Sequences 对TreeSet的元素进行排序。

TreeSet(Comparator<E> c)

Comparatorinterface本质上是:

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

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

TreeSet(Comparator<? super E> c)

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

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

首次try生成此方法签名会产生:

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完全等同于**。唯一需要的是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)为 Client 提供更大的灵 Active。

Wildcard Capture

到现在应该已经很清楚了:

Set<?> unknownSet = new HashSet<String>();
...
/* Add an element  t to a 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); // This works! Why?

看来这不应该被允许;但是,查看此特定电话,允许它是绝对安全的。毕竟,unmodifiableSet()可以对任何类型的Set起作用,无论其元素类型如何。

因为这种情况相对频繁发生,所以有一条特殊的规则允许在非常特殊的情况下使用此类代码,在这种情况下,可以证明该代码是安全的。该规则称为通配符catch,它允许编译器将通配符的未知类型作为泛型方法的类型参数推断出来。