Java泛型(6):extends和super关键字
(1) <T extends A>
因为擦除移除了类型信息,而无界的泛型参数调用的方法只等同于Object。但是我们可以限定这个泛型参数为某个类型A的子集,这样泛型参数声明的引用就可以用类型A的方法了,语法为<T extends A>。下面是一个例子:
1 // 超能 2 interface SuperPower { } 3 // 千里眼 4 interface SuperVision extends SuperPower { void see(); } 5 // 顺风耳 6 interface SuperHearing extends SuperPower { void hear(); } 7 8 // 超级英雄 9 class SuperHero<P extends SuperPower> { 10 P power; 11 SuperHero(P power) { this.power = power; } 12 P getPower() { return power; } 13 } 14 15 // 只会千里眼的英雄 16 class SuperVisionMan<P extends SuperVision> extends SuperHero<P> { 17 SuperVisionMan(P power) { super(power); } 18 void see() { power.see(); } 19 } 20 21 // 只会顺风耳的英雄 22 class SuperHearingMan<P extends SuperHearing> extends SuperHero<P> { 23 SuperHearingMan(P power) { super(power); } 24 void hear() { power.hear(); } 25 } 26 27 // 都会的的英雄 28 class SuperAllSkillsMan<P extends SuperVision & SuperHearing> extends SuperHero<P> { 29 SuperAllSkillsMan(P power) { super(power); } 30 void see() { power.see(); } 31 void hear() { power.hear(); } 32 } 33 34 class SampleSuperVision implements SuperVision{ 35 @Override 36 public void see() { System.out.println("I can see anything!"); } 37 } 38 39 class SampleSuperHearing implements SuperHearing{ 40 @Override 41 public void hear() { System.out.println("I can hear anything!"); } 42 } 43 44 class SampleSuperAllSkills implements SuperVision, SuperHearing{ 45 @Override 46 public void see() { System.out.println("I'm good at all skills and i can see anything!"); } 47 @Override 48 public void hear() { System.out.println("I'm good at all skills and i can hear anything!"); } 49 } 50 51 public class EpicBattle { 52 public static void main(String[] args) { 53 SuperVisionMan<SuperVision> man1 = new SuperVisionMan<>(new SampleSuperVision()); 54 man1.see(); // I can see anything! 55 SuperHearingMan<SuperHearing> man2 = new SuperHearingMan<>(new SampleSuperHearing()); 56 man2.hear(); // I can hear anything! 57 SuperAllSkillsMan<SampleSuperAllSkills> man3 = new SuperAllSkillsMan<>(new SampleSuperAllSkills()); 58 man3.see(); // I'm good at all skills and i can see anything! 59 man3.hear(); // I'm good at all skills and i can hear anything! 60 } 61 }
(2) <? extends T> / <? super T>
前置条件:
1 class Fruit { 2 private String name; 3 public Fruit(String name) { this.name = name; } 4 public String getName() { return name; } 5 @Override public String toString() { return name; } 6 } 7 class Apple extends Fruit { 8 public Apple(String name) { super(name); } 9 } 10 11 class RedFushi extends Apple { 12 public RedFushi(String name) { super(name); } 13 } 14 15 class Orange extends Fruit { 16 public Orange(String name) { super(name); } 17 }
我们先研究一种特殊的数组行为:可以向导出类的数组赋予基本类型的数组引用。这种行为是可以的。但是,如果往这个导出类的数组中插入其他类型的值(extends 基本类型),编译期不会报错,但是运行期则会报错。
1 Fruit[] fruits = new Apple[10]; 2 fruits[0] = new Apple("Apple1"); 3 fruits[1] = new Orange("Orange1"); // Compile OK; Run java.lang.ArrayStoreException.
如果替换成List容器,则编译期就会报错。原因与向上转型(Apple->Fruit)无关,根本原因在于ArrayList<Apple>和List<Fruit>不是同一类型。
1 List<Fruit> fruitList = new ArrayList<Apple>(); // [Compile ERROR] Type mismatch: cannot convert from ArrayList<Apple> to List<Fruit>
通过使用通配符<? extends Fruit>可以解决这个问题。 Apple是Fruit的子类型,则List<Apple>是 List<? extends Fruit>的子类型。但是一旦执行了这种向上转型,就失去了向其中传入任何对象的能力。你不能够往一个使用了<? extends T>的数据结构里写入任何的值。原因非常的简单,你可以这样想:这个<? extends Fruit>通配符告诉编译器我们在处理一个类型Fruit的子类型,但我们不知道这个子类型究竟是什么。因为没法确定,为了保证类型安全,我们就不允许往里面加入任何这种类型的数据。但是可以get到Fruit类型的元素对象。
1 List<? extends Fruit> fruitList1 = new ArrayList<Apple>(); 2 fruitList1.add(new Apple("Apple1")); // [Compile ERROR] The method add(capture#1-of ? extends Fruit) in the type List<capture#1-of ? extends Fruit> is not applicable for the arguments (Apple) 3 fruitList1.add(new Object()); // [Compile ERROR] The method add(capture#2-of ?extends Fruit) in the type List<capture#2-of ? extends Fruit> is not applicable for the arguments (Object)
可以使用下面的方法代替:
1 List<Apple> tmpList1 = new ArrayList<>(Arrays.asList(new Apple("Apple1"), new Apple("Apple2"))); 2 fruitList1 = tmpList1; 3 System.out.println(fruitList1.get(0) + "/" + fruitList1.get(1)); // Apple1/Apple2 4 List<Orange> tmpList2 = new ArrayList<Orange>(Arrays.asList(new Orange("Orange1"), new Orange("Orange2"))); 5 fruitList1 = tmpList2; 6 System.out.println(fruitList1.get(0) + "/" + fruitList1.get(1)); // Orange1/Orange2
也可以通过逆变使用超类型通配符,Fruit是Apple的父类型,则List<Fruit>是List<? super Apple>的子类型。
1 List<? super Apple> fruitList2 = new ArrayList<Fruit>(); 2 fruitList2.add(new Apple("Apple1")); 3 fruitList2.add(new RedFushi("RedFushi1")); 4 fruitList2.add(new Orange("Orange1")); // [Compile ERROR] The method add(capture#9-of ? super Apple) in the type List<capture#9-of ? super Apple> is not applicable for the arguments (Orange) 5 fruitList2.add(new Fruit("Fruit1")); // [Compile ERROR] The method add(capture#9-of ? super Apple) in the type List<capture#9-of ? super Apple> is not applicable for the arguments (Fruit)
(3) PECS法则
总结<? extends T>/<? super T>通配符的特征,我们可以得出以下结论:
1. 如果你想在方法中从input参数里获取数据,使用<? extends T>通配符
2. 如果你想在方法中把对象写入一个input参数中,使用<? super T>通配符
3. 如果你既想存,又想取,那就别用通配符
PECS指“Producer Extends,Consumer Super”。换句话说,如果方法中的参数表示一个生产者,就使用<? extends T>;如果它表示一个消费者,就使用<? super T>。
1 import java.util.Collection; 2 import java.util.Stack; 3 4 public class MyStack<E> extends Stack<E> { 5 private static final long serialVersionUID = 1L; 6 // 如果你想在方法中从input参数里获取数据,使用<? extends T>通配符 7 public void pushAll(Collection<? extends E> params) { 8 for(E t : params) { push(t); } 9 } 10 // 如果你想在方法中把对象写入一个input参数中,使用<? super T>通配符 11 public void popAll(Collection<? super E> results) { 12 while(!empty()) { results.add(pop()); } 13 } 14 }
1 MyStack<Fruit> stack1 = new MyStack<Fruit>(); 2 Collection<Apple> appleList = new ArrayList<>(Arrays.asList(new Apple("Apple1"), new Apple("Apple2"))); 3 Collection<Orange> orangeList = new ArrayList<>(Arrays.asList(new Orange("Orange1"), new Orange("Orange2"))); 4 Collection<Fruit> fruitList = new ArrayList<>(Arrays.asList(new Apple("Apple3"), new RedFushi("RedFushi3"), new Orange("Orange3"))); 5 stack1.pushAll(appleList); 6 stack1.pushAll(orangeList); 7 stack1.pushAll(fruitList); 8 Collection<Fruit> resultList = new ArrayList<>(); 9 stack1.popAll(resultList); 10 for (Fruit res : resultList) { 11 System.out.print("[" + res.getName() + "--" + res.getClass().getSimpleName() + "] "); 12 // [Orange3--Orange] [RedFushi3--RedFushi] [Apple3--Apple] [Orange2--Orange] [Orange1--Orange] [Apple2--Apple] [Apple1--Apple] 13 }