【问题标题】:Lower bounded wildcard (Comparable<? super K>)下界通配符(Comparable<?super K>)
【发布时间】:2026-01-21 12:00:02
【问题描述】:

在集合中经常使用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);

如果我正确理解了通配符的概念,使用&lt;? super T&gt; 而不是&lt;T&gt; 的唯一效果是编译器扩展了compareTo 的可能参数类型

public interface Comparable<T> {
    public int compareTo(T o);
}

Integer 的任何超类。 但我想知道为什么以及如何在这里使用它。 任何示例

Comparable<T> key = (Comparable<T>) x;

还不够吗?或者将“超级”通配符与 Comparable 一起使用是一种指导方针?

【问题讨论】:

    标签: java generics collections wildcard


    【解决方案1】:

    放宽泛型签名可以提高代码的灵活性,尤其是在我们讨论参数类型时。一般准则是 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&lt;ChronoLocalDate&gt;

    所以,方法签名必须是

    public static <T extends Comparable<? super T>> void sort(Collection<T> c) {
    
    }
    

    允许实际类型使用超类型参数化Comparable,就像声明了Collections.sort一样。


    对于siftUpComparable的具体情况,即内部Collection方法没有机会确定实际的泛型签名,使用Comparable&lt;T&gt;Comparable&lt;? super T&gt;确实没有关系,只要它需要,是将T 的实例传递给compare 方法的能力,而即使提供实际参数也是由另一个未经检查的强制转换完成的。

    事实上,由于类型转换未检查,它也可以转换为Comparable&lt;Object&gt;,这样就无需在compare 进行(T) 转换调用。

    但很明显,作者并不打算与实际的泛型代码所做的和使用相同的模式相差太远,即使它在这里没有明显的好处。

    【讨论】:

    • 优秀的答案!我认为这是一种模式和我以前的工作,但我一直不明白为什么,我一直对自己说 - 我错过了一些东西......
    【解决方案2】:

    E 实际上可能不会为其自己的类公开可比较对象。想象一下这种情况:

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

    如果您将优先级队列设为PriorityQueue&lt;Bar&gt;,则使用Foo 中定义的比较方法。即,您有一个Comparable&lt;? super Bar&gt;,其中通配符实现为Foo

    【讨论】:

    • 经过测试,这也适用于没有通配符的情况。你怎么能不将Comparable 暴露给子类呢?我的意思是bar1.compareTo(bar2) 无论如何都是合法的(即使compareTo 得到Foo 类型的参数)。
    • @A.King Casting 在泛型类型参数中的工作方式与在*类中的工作方式不同。例如,您不能直接将List&lt;Integer&gt; 转换为List&lt;Object&gt;,就像您可以将ArrayList&lt;Integer&gt; 转换为List&lt;Integer&gt; 一样。这就是? 通配符和extend/super 语法存在的原因。
    • 我似乎明白了通配符在这个例子中是如何工作的,但是,它与没有通配符的选项没有什么不同。如果我显式转换为Comparable&lt;Bar&gt;,它的编译和工作方式与Comparable&lt;Foo&gt; 相同。
    • @A.King 您执行了 Unsafe-Cast 并且由于泛型类/方法范围的限制而只能偶然工作。你可能有一个警告要告诉你。唯一可行的原因是运行时的类型擦除; Comparable&lt;Bar&gt;Comparable&lt;Foo&gt; 在运行时都将减少到 Comparable&lt;Object&gt;。在非通用上下文中,Comparable&lt;Bar&gt; b =(Comparable&lt;Bar&gt;) new Bar(); 将无法与 error: incompatible types: Bar cannot be converted to Comparable&lt;Bar&gt; 一起编译。相反,它必须定义为Comparable&lt;? super Bar&gt; b = new Bar();
    【解决方案3】:

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

    要了解泛型中superextends 的不同语义,请参阅此主题:
    What is PECS (Producer Extends Consumer Super)?

    【讨论】:

    • 仍然,不确定为什么在Comparable 的泛型定义中需要用户super