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 }

 

posted @ 2017-12-07 10:27  Storm_L  阅读(2814)  评论(0编辑  收藏  举报