如何理解 Java 中的 <T extends Comparable<? super T>>
Java 中类似 这样的类型参数 (Type Parameter) 在 JDK 中或工具类方法中经常能看到。比如 java.util.Collections 类中的这个方法声明:
public static <T extends Comparable<? super T>> void sort(List<T> list)
我知道 extends 和 super 这样的关键字在泛型中是干什么的,但对上面这样复杂的类型参数声明着实有点看不懂。
我觉得类型参数 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: }
上面的代码包括三个类:
Animal实现了Comparable接口,通过年龄来比较实例的大小Dog继承自Animal。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
所以,
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.Date是java.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 staticextends Comparable > void one(List t){ } //创建一个符合 >参数的方法 public staticextends 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