在本节中,我们将考虑通配符的一些更高级的用法。我们已经看到了几个示例,其中从数据结构读取数据时,有界通配符很有用。现在考虑相反的情况,只写数据结构。接口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); // Illegal call.
如所写,对writeAll()
的调用是非法的,因为无法推断出有效的类型参数。 String
和Object
都不是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
。
解决方案是使用一种尚未见过的有界通配符形式:具有下界(lower bound)的通配符。 语法? **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)
Comparator
接口本质上是:
interface Comparator<T> {
int compare(T fst, T snd);
}
假设我们要创建一个TreeSet<String>
并传入合适的比较器,我们需要向其传递一个可以比较Strings
的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
不必与自己完全可比。 所需要做的就是让T与它的超类型之一可比。 这给我们:
public static <T extends Comparable<? super T>>
T max(Collection<T> coll)
请注意,实际上涉及Collections.max()
的实际签名。 我们将在下一节“将旧版代码转换为使用泛型”中返回它。 这种推理几乎适用于可用于任意类型的Comparable
的几乎所有用法:您始终想使用Comparable<? **super** T>
。
通常,如果您有一个仅使用类型参数T
作为参数的API,则其使用应利用下界通配符? **super** T
。 相反,如果API仅返回T
,则可以使用上界通配符? **extends** T
来为客户提供更大的灵活性。
通配符捕获
到现在应该已经很清楚了:
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
,无论其元素类型如何。
由于这种情况经常发生,因此有一条特殊的规则允许在非常特殊的情况下使用此类代码,在这种情况下,可以证明该代码是安全的。 该规则称为通配符捕获( wildcard capture__),它允许编译器的推断将通配符的未知类型作为泛型方法的类型参数。