Java泛型中的通配符和<? super T>含义,下限或上限绑定

6

我正在阅读有关泛型方法的内容,但是我感到困惑。让我先在这里陈述问题:

在这个例子中:假设我需要一个适用于任何类型T的selectionSort版本,通过使用由调用者提供的外部可比较性。

第一次尝试:

public static <T> void selectionSort(T[] arr, Comparator<T> myComparator){....}

假设我已经完成以下步骤:
  • 定义了车辆类
  • 创建了实现比较器的VehicleComparator,以价格作为车辆之间的比较依据。
  • 创建了卡车类(Truck),继承自车辆类。
  • 实例化了Truck[] arr;VehicleComparator myComparator。
接下来,我要进行如下操作:
selectionSort(arr, myComparator);

但这样做是不行的,因为myComparator对于Vehicle的任何子类都不可用。

那么,我接着做了这个:

public static <T> void selectionSort(T[] arr, Comparator<? super T> myComparator){....}

这个声明可以工作,但我不完全确定自己在做什么... 我知道使用 是正确的方法。如果"? super T"意味着 "T的未知超类型",那么我是强制要求上限还是下限?为什么是super?我的意图是让任何T的子类使用myComparator,为什么要用"? super T"。非常困惑... 如果您有任何深入见解,我将不胜感激。

提前感谢!


1
这会对你有所帮助。http://www.thejavageek.com/2013/08/28/generics-the-wildcard-operator/ - Prasad Kharkar
这可能对您有用:https://dev59.com/52855IYBdhLWcg3wbzxl - Pawel
这也可能对你有所帮助。Effective Java 这本书建议记住这个 - PECS(Producer extends,Consumer super。用 extends 来生产类,并用 super 来消费类。 - KrishPrabakar
@PrasadKharkar 非常感谢!这篇博客非常好,甚至还有一个例子! - Victoria J.
这是错误的。myComparator 确实比较 Vehicle。而且 Truck[]Vehicle[] 的子类。 - newacct
4个回答

5
首先,你可以通过使用 Vehicle[],然后向其中添加Truck来解决它。
需要使用 <? super T> 的原因是基于泛型规则,Comparator<Truck> 不是 Comparator<Vehicle> 的子类型;无界类型 T 必须完全匹配,而它并不匹配。
为了传入适当的 Comparator,它必须是被比较类或其任何超类的 Comparator,因为在面向对象语言中,任何类都可以被视为超类的实例。因此,Comparator 的泛型类型并不重要,只要它是数组组件类型的超类型即可。

但是谁说T代表卡车了呢?如果你选择T代表“车辆”,那么它就可以工作。 - newacct
@newacct 如果您仔细阅读问题,就会发现他实例化了 Truck[] arr,然后将其传递给 T[],因此Java推断 TTruck。毫无疑问。 - Bohemian
1
当然,他的编译器可能会这样推断。但是如果推断不足,他总可以手动提供类型参数。你说“你需要<? super T>”,这并不正确,因为它可以在没有它的情况下工作。 - newacct

4

疑问的短语? super T表示目标列表可能包含任何T的超类型元素,就像源列表可以包含任何T的子类型元素一样。

我们可以从Collections中看到一个非常简单的copy示例:

public static <T> void copy(List<? super T> dst, List<? extends T> src) {
   for (int i = 0; i < src.size(); i++) {
    dst.set(i, src.get(i));
   }
}

并调用:

List<Object> objs = Arrays.<Object>asList(2, 3.14, "four");
List<Integer> ints = Arrays.asList(5, 6);
Collections.copy(objs, ints);
assert objs.toString().equals("[5, 6, four]");

像任何通用方法一样,类型参数可以被推断或明确给出。在这种情况下,有四种可能的选择,所有这些选择都可以进行类型检查,并且所有这些选择都具有相同的效果:

Collections.copy(objs, ints);
Collections.<Object>copy(objs, ints);
Collections.<Number>copy(objs, ints);
Collections.<Integer>copy(objs, ints);

如果我错了,请纠正我。那么“? super T”可以是上限或下限,这取决于我把这个短语放在哪里,对吗? - Victoria J.
1
super 是一个下限,而 extends 是一个上限。 - Maxim Shoustin

2
你的方法签名
public static <T> void selectionSort(T[] arr, Comparator<? super T> myComparator)

这意味着如果你使用类型为T的数组调用它,那么你必须提供一个类型为T或T的超类型的Comparator

例如,如果你有以下类:

class Vehicle {}

class Truck extends Vehicle {}

class BigTruck extends Truck {}

class VehicleComparator implements Comparator<Vehicle> {    
    public int compare(Vehicle o1, Vehicle o2) {
        return 0;
    }
}

class BigTruckComparator implements Comparator<BigTruck> {
    public int compare(BigTruck o1, BigTruck o2) {
        return 0;
    }
}

class TruckComparator implements Comparator<Truck> {
    public int compare(Truck o1, Truck o2) {
        return 0;
    }
}

那么这将起作用。
Truck[] trucks = ...;
selectionSort(trucks, new TruckComparator());
selectionSort(trucks, new VehicleComparator());

由于

  • TruckComparator 实现了 Comparator<Truck>,而 Truck 等于数组类型的 Truck
  • VehicleComparator 实现了 Comparator<Vehicle>,而 Vehicle 是数组类型 Truck 的超类型

这种方法不起作用

selectionSort(trucks, new BigTruckComparator());

因为BigTruckComparatorComparator<BigTruck>类型,而BigTruck不是数组类型Truck的超类型。

1
这两个签名在功能上是等效的——对于任何一组参数,如果存在适用于其中一个签名的类型参数选择,那么也存在适用于另一个签名的类型参数选择,反之亦然。
你只是在编译器中遇到了有限的推断。只需明确指定所需的类型参数即可。
YourClass.<Vehicle>selectionSort(arr, myComparator);

虽然这是因为数组是协变的(Truck[]Vehicle[]),所以这是正确的,但值得一提的是,如果该方法取代了 List<T>,则需要使用 Comparator<? super T> - Paul Bellora

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接