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 }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM