在 Java 泛型中使用通配符

Using wildcards in Java generics

我正在使用 Y. Daniel Liang 的书“Introduction to Java Programming and Data Structures”来学习Java泛型,但我没有不理解其中一项练习:

"19.3 (Pair of objects of same type) 创建一个封装两个对象的Pair class 在 Pair.

的实例中具有相同的数据类型

19.4(使用通配符)编写一个通用静态方法,returns 来自编程练习 19.3."

的 Pair 实例

解决代码如下:


public class Exercise19_04 {

    public static class Pair<T> {
        public final T one;
        public final T two;

        public Pair(T one, T two) {
            this.one = one;
            this.two = two;
        }
    }
    
    public static class Main {
        // To have the smallest between two objects, they need to be comparable.
        public static <T extends Comparable<? super T>> T smallest(Pair<T> p) {
            if (p.one.compareTo(p.two) < 0) {
                return p.one;
            } else {
                return p.two;
            }
        }
        
        public static void main(String[] args) {
            Pair<Double> pd = new Pair<>(7.0, 6.3);
            System.out.println(smallest(pd));
            Pair<String> ps = new Pair<>("big", "small");
            System.out.println(smallest(ps));
            /* Lines below are not compilable because Color is not comparable
            Pair<String> pc = new Pair<>(java.awt.Color.BLUE, java.awt.Color.RED);
            System.out.println(smallest(ps));*/
        }
    }
}

我的问题是方法声明public static <T extends Comparable<? super T>> T smallest(Pair<T> p)

我知道为了比较 Pair 实例中的对象 T,T 必须是 Comparable 的实例,因此您必须声明 <T extends Comparable>。但是我不明白 <? super T> 之后的部分。有人可以向我解释一下吗?提前致谢!

暂时忘掉 Pair。让我们看看Comparable。一开始人们可能认为 Comparable 不需要参数。但这有一个问题。 compareTo(如 a.compareTo(b))的签名是什么样的?

也许:

/**
 * Return negative if a is 'before' b, positive if a is 'after' b,
 * and 0 if they are equal in ranking.
 */
 public int compareTo(Object other);
}

但这是有问题的:现在例如Double 要实现 Comparable,它需要能够回答 任何类型 的问题。因此,如果我尝试将值为 4.5 的 Double 与 InputStream 进行比较 - 这是一个荒谬的问题。要求对比枪和奶奶

因此,我们强烈希望签名为:

/**
 * Return negative if a is 'before' b, positive if a is 'after' b,
 * and 0 if they are equal in ranking.
 */
 public int compareTo(Double other);
}

但是怎么办?我们不能为每个存在的类型创建一个新的 Comparator 接口,并且 java 中没有任何东西意味着 'your own type'.

泛型来拯救!让我们创建一个泛型参数来表示您可以与自己进行比较的类型:

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

让我们用双倍的方式实现它:

public class Double extends Number implements Comparable<Double> {
 ...

我们将类型 Comparable 解读为:“foo 的可比较对象”,意思是:可以与 Foo 类型的对象进行比较的对象。 (通常,我们只是指 Foos;当然,我们有兴趣将橙子与橙子进行比较)。

现在让我们把它贴回 Pair。我们不能写 Pair<Comparable> 的原因与您不能写 List<List> 的原因相同。是的,它会编译,但你会收到严重的警告:毕竟,好的列表列表。但是列表中的列表是什么?你会写 List<List<String>>: 字符串列表的列表。这同样适用于可比较对象:它不仅仅是一对可比较对象,而是一对 Foos 的可比较对象。 Pair<Comparable<String>> 表示:我有一对 2 个东西,每个东西都是某种知道如何将自己与字符串进行比较的类型。 (而且,剧透警告,只有 java.lang.String 符合该要求)。

这只剩下两个问题:

  1. 为什么是 Pair<? extends Comparable<..>> 而不仅仅是 Pair<Comparable>
  2. 为什么是 Pair<... Comparable<? super T>> 而不仅仅是 Comparable<T>

这些问题的答案:

  1. 因为 PECS。请参阅评论中的链接答案。
  2. 这很少相关,但从理论上讲,如果您有某种类型的对象能够将自身与某种超类型的字符串进行比较(比如 'can compare itself to any object of any type'),那么,那会..就好了。我们想要任何在我喂它时知道该做什么的东西:嘿,把你自己比作这个东西,这个东西是 T 型的。不管它是什么。如果这个东西可以将自己与 T 的某个超类型进行比较,那就完全没问题了。所有整数也是数字 - 所有 T 也是 T 的任何超类型。