如何理解 Java 中的 <T extends Comparable<? super T>>
1 <T extends Comparable<T>>
和 <T extends Comparable<? super T>>
有什么不同
<T extends Comparable<T>>
- 类型 T 必须实现
Comparable
接口,并且这个接口的类型是 T。只有这样,T 的实例之间才能相互比较大小。例如,在实际调用时若使用的具体类是 Dog,那么 Dog 必须implements Comparable<Dog>
<T extends Comparable<? super T>>
- 类型 T 必须实现
Comparable
接口,并且这个接口的类型是 T 或 T 的任一父类。这样声明后,T 的实例之间,T 的实例和它的父类的实例之间,可以相互比较大小。例如,在实际调用时若使用的具体类是 Dog (假设 Dog 有一个父类 Animal),Dog 可以从 Animal 那里继承Comparable<Animal>
,或者自己implements Comparable<Dog>
。 -
2 我对
<T extends Comparable<? super T>>
类型参数的理解光看上面的定义除了摸不着头脑,不会有其它感觉。下面用代码来说明为什么要这样声明。
2.1 代码运行环境
我使用的 JDK 版本是:
1.8.0_60
,在 Eclipse 中编译运行。因为注释用了中文,编码采用 UTF-8。如果你要在命令行下编译、运行,编译时要使用-encoding UTF-8
选项:javac -encoding UTF-8 TypeParameterTest.java
另外,Eclipse 中的警告、错误信息跟命令行中的不一样(个人感觉 Eclipse 中的信息要好懂一些)。以下的示例以 Eclipse 中的信息为准。
2.2 示例代码
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<Animal>
接口,通过年龄来比较实例的大小Dog
继承自Animal
。TypeParameterTest
类中提供了两个排序方法和测试用的main()
方法:mySort1()
使用<T extends Comparable<T>>
类型参数mySort2()
使用<T extends Comparable<? super T>>
类型参数main()
测试方法。在这个方法中要创建一个Animal List
和一个Dog List
,然后分别调用两个排序方法
2.3 测试 mySort1() 方法
1: // 创建一个 Animal List 2: List<Animal> animals = new ArrayList<Animal>(); 3: animals.add(new Animal(25)); 4: animals.add(new Dog(35)); 5: 6: // 创建一个 Dog List 7: List<Dog> dogs = new ArrayList<Dog>(); 8: dogs.add(new Dog(5)); 9: dogs.add(new Dog(18)); 10: 11: // 测试 mySort1() 方法 12: mySort1(animals); 13: mySort1(dogs);
Line 13 出编译错误了。Eclipse 说:
The method mySort1(List<T>) in the type TypeParameterTest is not applicable for the arguments (List<Dog>)
为什么会出错误呢? mySort1() 方法的类型参数是 <T extends Comparable<T>> ,它要求的类型参数是类型为 T 的
Comparable
。如果传入的是
List<Animal>
,没问题,因为Animal implements Comparable<Animal>
。但是,如果传入的参数是
List<Dog>
有问题,因为 Dog 没有implements Comparable<Dog>
,它只是从 Animal 继承了一个Comparable<Animal>
。不知道大家注意到没有,那个 animals list 中实际上是包含一个 Dog 实例的。如果你碰上类似的情况(子类 list 不能传入到一个方法中),可以考虑把子类实例放到一个父类 List 中,避免编译错误。
2.4 测试 mySort2() 方法
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<Animal>
这样的参数,也可以接收:Dog implements Comparable<Animal>
这样的参数。2.5 Dog 可以
implements Comparable<Dog>
吗?如果让 Dog implements Comparable<Dog> 不也可以解决前面的那个编译错误吗?
1: class Dog extends Animal implements Comparable<Dog> 2: { 3: public Dog(int age) 4: { 5: super(age); 6: } 7: }
很不幸,出错了。Eclipse 说:
The interface Comparable cannot be implemented more than once with different arguments: Comparable<Animal> and Comparable<Dog>
就是说,Dog 已经从父类 Animal 那里继承了一个
Comparable
,它不能再实现一个Comparable
。如果子类不喜欢父类的实现怎么办? Override 父类的
public int compareTo(Animal other)
方法。2.6
<T extends Comparable<? super T>>
类型参数声明的好处对 Animal/Dog 这两个有父子关系的类来说:
<T extends Comparable<? super T>>
可以接受 List<Animal> ,也可以接收 List<Dog> 。 而<T extends Comparable<T>>
只可以接收 List<Animal>所以,<T extends Comparable<? super T>> 这样的类型参数对所传入的参数限制更少,提高了 API 的灵活性。总的来说,在保证类型安全的前提下,要使用限制最少的类型参数。