在集合中经常使用转换为Comparable
界面,例如。在PriorityQueue
:
private static <T> void siftUpComparable(int k, T x, Object[] es) {
Comparable<? super T> key = (Comparable<? super T>) x;
...
if (key.compareTo((T) e) >= 0)
break;
...
}
比如说,我们创建整数队列并添加一些内容:
PriorityQueue<Integer> queue = new PriorityQueue<>();
queue.add(1);
如果我正确地得到通配符的概念,使用<? super T>
而不是<T>
的唯一效果是编译器扩展了compareTo
的可能参数类型
public interface Comparable<T> {
public int compareTo(T o);
}
任何超级的Integer
。但我想知道为什么以及如何在这里使用它。任何一个例子
Comparable<T> key = (Comparable<T>) x;
是不足够的?或者它是使用“超级”通配符与可比较的指南?
放宽通用签名可以提高代码的灵活性,尤其是在我们讨论参数类型时。一般准则是PECS规则,但对于Comparable
,需要这种灵活性的实际例子是罕见的,因为这些暗示具有在其所有子类型中定义自然顺序的基本类型。
例如,如果您有类似的方法
public static <T extends Comparable<T>> void sort(Collection<T> c) {
}
你不能做以下
List<LocalDate> list = new ArrayList<>();
sort(list); // does not compile
原因是LocalDate
实现了ChronoLocalDate
,你可以将ChronoLocalDate
的所有实现相互比较,换句话说,它们都实现了Comparable<ChronoLocalDate>
。
因此,方法签名必须是
public static <T extends Comparable<? super T>> void sort(Collection<T> c) {
}
允许实际类型实现用超类型参数化的Comparable
,就像已声明Collections.sort
一样。
对于siftUpComparable
的具体情况,即没有机会确定实际通用签名的内部收集方法,它是否使用Comparable<T>
或Comparable<? super T>
确实无关紧要,因为它需要的是传递T
实例的能力对compare
方法,然而即使提供实际参数也是由另一个未经检查的演员完成的。
事实上,由于类型转换是未经检查的,它也可能是对Comparable<Object>
的强制转换,这将消除在(T)
调用时执行compare
演员的需要。
但显然,作者并不打算离开实际通用代码所做的太远而使用相同的模式,即使它在这里没有明显的好处。
E
实际上可能不会为自己的类暴露出类似的东西。想象一下这个案例:
class Foo implements Comparable<Foo> { ... }
class Bar extends Foo { ... }
如果你创建PriorityQueue<Bar>
的优先级队列,那么你使用Foo
中定义的比较方法。即,你有一个Comparable<? super Bar>
,其中通配符被实现为Foo
仿制药的重点是类型安全。由于Java泛型在运行时丢失了类型信息,因此我们没有验证此强制转换的方法。有界通配符是一种在编译时验证类型限制的安全方法,而不是在运行时制作一个hail Mary。
要理解泛型中super
和extends
的不同语义,请参考以下主题:
What is PECS (Producer Extends Consumer Super)?