如何理解 Java 中的 <T extends Comparable<? super T>>


>表明T实现了Comaprable接口,此条件强制约束,泛型对象必须直接实现Comparable所谓直接就是指不能通过继承或其他方式

> 表明T的任意一个父类实现了Comparable<? super T>接口,其中? super T表示 ?泛型类型是T的父类(当然包含T),因此包含上面的限制条件,且此集合包含的范围更广

Java 中类似 > 这样的类型参数 (Type Parameter) 在 JDK 中或工具类方法中经常能看到。比如 java.util.Collections 类中的这个方法声明:

public static <T extends Comparable<? super T>> void sort(List<T> list)

我知道 extendssuper 这样的关键字在泛型中是干什么的,但对上面这样复杂的类型参数声明着实有点看不懂。

我觉得类型参数 T 写成这样就足够了:

extends Comparable<T>>

可 T 偏偏被声明成这样:

extends Comparable<? super T>>

搞这么复杂图啥呢?难道 Java 只是高智商人士的玩具?

平常写工具类的机会比较少,上面的方法参数类型看不懂或写不出来问题倒也不大。只要知道怎么调用这些方法,日子就能混过去。我估计像我这样混日子的程序员不少吧。

终于有一天,我觉得有点对不起 Java Developer 这个头衔了,于是认真看了看书,认真 Google 了一下,终于搞明白了这样的类型参数是怎么回事儿。

1 >> 有什么不同

光看上面的定义除了摸不着头脑,不会有其它感觉。下面用代码来说明为什么要这样声明。

2.1 代码运行环境

 1: package generics3;
 2: 
 3: import java.util.ArrayList;
 4: import java.util.Collections;
 5: import java.util.List;
 6: 
 7: public class TypeParameterTest
 8: {
 9:     //第一种声明:简单,灵活性低
10:     public static <T extends Comparable<T>> void mySort1(List<T> list)
11:     {
12:         Collections.sort(list);
13:     }
14: 
15:     //第二种声明:复杂,灵活性高
16:     public static <T extends Comparable<? super T>> void mySort2(List<T> list)
17:     {
18:         Collections.sort(list);
19:     }
20: 
21:     public static void main(String[] args)
22:     {
23:         //在这个方法中要创建一个 Animal List 和一个 Dog List,然后分别调用两个排序方法。
24:     }
25: }
26: 
27: class Animal implements Comparable<Animal>
28: {
29:     protected int age;
30: 
31:     public Animal(int age)
32: 
33:     {
34:         this.age = age;
35:     }
36: 
37:     //使用年龄与另一实例比较大小
38:     @Override
39:     public int compareTo(Animal other)
40:     {
41:         return this.age - other.age;
42:     }
43: }
44: 
45: class Dog extends Animal
46: {
47:     public Dog(int age)
48:     {
49:         super(age);
50:     }
51: }

上面的代码包括三个类:

  1. Animal 实现了 Comparable 接口,通过年龄来比较实例的大小
  2. Dog 继承自 Animal
  3. TypeParameterTest 类中提供了两个排序方法和测试用的 main() 方法:
    • mySort1() 使用 > 类型参数
    • mySort2() 使用 > 类型参数
    • main() 测试方法。在这个方法中要创建一个 Animal List 和一个 Dog List ,然后分别调用两个排序方法

2.3 测试 mySort1() 方法

 1: public static void main(String[] args)
 2: {
 3:     // 创建一个 Animal List
 4:     List<Animal> animals = new ArrayList<Animal>();
 5:     animals.add(new Animal(25));
 6:     animals.add(new Dog(35));
 7: 
 8:     // 创建一个 Dog List
 9:     List<Dog> dogs = new ArrayList<Dog>();
10:     dogs.add(new Dog(5));
11:     dogs.add(new Dog(18));
12: 
13:     // 测试  mySort2() 方法
14:     mySort2(animals);
15:     mySort2(dogs);
16: }

两个方法调用都没有问题。 第二个方法不但可以接受 Animal implements Comparable 这样的参数,也可以接收: Dog implements Comparable 这样的参数。

2.5 Dog 可以 implements Comparable 吗?

对 Animal/Dog 这两个有父子关系的类来说: > 可以接受 List ,也可以接收 List 。 而 > 只可以接收 List

所以,> 这样的类型参数对所传入的参数限制更少,提高了 API 的灵活性。总的来说,在保证类型安全的前提下,要使用限制最少的类型参数。

3 其他

JDK 中这样的例子很多,比如 java.util.Date 和 java.sql.Date 这两个类:

public class Date
    implements java.io.Serializable, Cloneable, Comparable<Date>
public class Date extends java.util.Date
  • java.sql.Datejava.util.Date 的子类。
  • java.util.Date 实现了 Comparable~,所以 ~java.sql.Date 也拥有了 Comparable 类型。
  • java.sql.Date 不能再 implements Comparable
  • 如果你有一个 List 并对它排序的话,只能传给拥有 > 这种类型参数的方法。

3.2 《Effective Java》 一书对 > 这种类型参数的解释

简单的泛型很好理解很好用,但稍微复杂一点,就变得很难理解。

3.3.1 脑子开窍开大了

有时候想得多了,脑子就糊涂了,一点儿也不开窍,连简单问题也不明白了。 比如,我可以这样定义一个方法:

public static <T extends Animal> void mySort3(List<T> list)
{
    Collections.sort(list);
}

也可以这样定义一个方法:

public static void mySort4(List<? extends Animal> list)
{
    Collections.sort(list);
}

第二个方法没有 T ,也能实现跟第一个方法同样的功能,我为什么非得要一个 T 呢?在脑子思虑过度的情况下,进死胡同了。在我准备放狗搜之前,总算想明白了。

第二个方法中,参数是: List<? extends Animal> list 。 这个方法可以接收 List ,也可以接收 List 。这里没有使用类型参数,只是使用泛型的限定符对所传入的 List 的类型做了一个限定。

而在第一个方法中,使用了一个类型参数 T 。这个 T 可以是 Animal ,也可以是 Animal 的子类 Dog。

在第一个方法中,看不出定义一个类型参数有什么作用。但是,类型参数不但可以在方法参数中使用,也可以在方法返回值和方法体内使用。比如下面这个方法:

1: public <T extends Comparable<? super T>> T test1(T t, List<T> list)
2: {
3:     for (T element : list)
4:     {
5:         if (element.equals(t))
6:             return t;
7:     }
8:     return null;
9: }

你定义了一个类型参数 T ,这个 T 定义成 : > 。定义好之后,你就可以在参数中,返回值中,以及方法体内使用这个 T 了。如果不使用类型参数,是达不到这种效果的。

你也可以定义多个类型参数,并让这些参数之间有关联:

1: public <T, S extends T> T test2(T t, S s)
2: {
3:     return s;
4: }

3.3.3 多练习练习

1.T extends Comparable<? super T> 可以将 Comparable<? super T> 看作一个整体,也就是说,T需要继承(实现)Comparable接口。

2.Comparable<? super T>可以理解为Comparable的参数类型可以是T或者T的父类

3.结合起来理解,该类传入的参数需要实现了Comparable接口,而接口的参数必须是T或者T的父类(接口)。

4.基于 3 既然这样,为什么不直接使用> 呢?

很简单,因为这样不是不能用,而是限制了传入参数的类型!只能做到传入的参数T实现了Comparable接口而且Comparable的参数为T,那要是Comparable的参数是别的呢?是参数T的父类或者子类呢?为了方便理解,这里举一个例子: 

public class Boy extends People{
    public Boy(Integer age) {
        super(age);
    }
}

public class People implements Comparable{
    private Integer age;

    public People(Integer age) {
        this.age = age;
    }

    @Override
    public int compareTo(People people) {
        return this.age-people.age;
    }
}

测试类
public class Test {
    //创建一个符合>参数的方法
    public static extends Comparable> void one(List t){
    }

    //创建一个符合>参数的方法
    public static extends Comparable<? super T>> void two(List t){
    }

    public static void main(String[] args) {
        //构建list1
        List list1 = new ArrayList<>();
        list1.add(new People(20));
        list1.add(new Boy(12));

        //构建list2
        List list2 = new ArrayList<>();
        list2.add(new Boy(13));
        list2.add(new Boy(14));

        //符合预期,相当于>
        Test.one(list1);
        //不符合预期,相当于>,提示编译期错误
        Test.one(list2);

        //符合预期,相当于>
        Test.two(list1);
        //符合预期,相当于>
        Test.two(list2);
    }
}

其中Test.one(list2)发生了编译期错误:

这里错误的原因是 Boy因为继承的缘故,间接地实现了Comparable< People>接口,与参数类型的> —> >不符合 。

总结
看到这里大概就可以知道了,在某些特殊例子中,>兼容性并不是很好,所以采用了>的形式,目的就是为了更好地兼容,很好地体现了泛型的特性。
 

来源:

https://www.cnblogs.com/xiaomiganfan/p/5390252.html

https://blog.csdn.net/weixin_44308662/article/details/108970606