(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 }
