下界通配符(Comparable <!-?super K->)

时间:2019-02-13 22:02:22

标签: java generics collections wildcard

在集合中,经常使用强制转换为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;

还不够吗?还是在Comparable中使用“超级”通配符是一种指导?

3 个答案:

答案 0 :(得分:2)

E可能实际上未公开其同类产品的同类产品。想象这种情况:

class Foo implements Comparable<Foo> { ... }

class Bar extends Foo { ... }

如果将优先级队列设置为PriorityQueue<Bar>,则可以使用Foo中定义的比较方法。也就是说,您有一个Comparable<? super Bar>,其中通配符被实现为Foo

答案 1 :(得分:2)

放松通用签名可以提高代码的灵活性,尤其是在我们谈论参数类型时。一般准则是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的特定情况,即内部的Collection方法没有机会确定实际的通用签名,是否使用Comparable<T>Comparable<? super T>的确无关紧要所需要的就是将T的实例传递给compare方法的能力,而即使提供实际的参数也要由另一个未经检查的强制转换完成。

实际上,由于类型强制转换是 unchecked ,因此它也可能是强制转换为Comparable<Object>的类型,这样就无需在以下位置进行(T)强制转换compare调用。

但是很明显,即使在这里并没有明显的好处,作者也不打算远离实际的通用代码,而是使用相同的模式。

答案 2 :(得分:1)

泛型的重点是类型安全。由于Java泛型在运行时会丢失类型信息,因此我们没有验证此类型转换的方法。有界通配符是一种在编译时验证类型限制的安全方法,而不是在运行时进行一次Mary强制转换。

要了解泛型superextends的不同语义,请查询以下线程:
What is PECS (Producer Extends Consumer Super)?