泛型的繼承關系:Pair<Integer>
不是Pair<Number>
的子類。
extends通配符
1 import java.util.*; 2 public class Demo13{ 3 4 public static void main(String[] args) throws Exception{ 5 //實例化一個Pair<Integer>泛型類 6 Pair<Integer> p = new Pair<>(12,34); 7 int n = add(p); 8 System.out.println(n); 9 } 10 11 //此靜態方法形參是傳入一個Pair<Number>的泛型實例 12 static int add(Pair<Number> p){ 13 Number first = p.getFirst(); 14 Number last = p.getLast(); 15 //Number類型使用intValue()返回int類型 16 return first.intValue() + last.intValue(); 17 } 18 } 19 20 //定義一個泛型類 21 class Pair<T> { 22 private T first; 23 private T last; 24 public Pair(T first,T last){ 25 this.first = first; 26 this.last = last; 27 } 28 public T getFirst(){ 29 return first; 30 } 31 public T getLast(){ 32 return last; 33 } 34 }
原因很明顯,因為Pair<Integer>
不是Pair<Number>
的子類,因此,add(Pair<Number>)
不接受參數類型Pair<Integer>
。
但是從add()
方法的代碼可知,傳入Pair<Integer>
是完全符合內部代碼的類型規范,因為語句:
Number first = p.getFirst();
Number last = p.getLast();
實際類型是Integer
,引用類型是Number
,沒有問題。問題在於方法參數類型定死了只能傳入Pair<Number>
。
有沒有辦法使得方法參數接受Pair<Integer>
?辦法是有的,這就是使用Pair<? extends Number>
使得方法接收所有泛型類型為Number
或Number
子類的Pair
類型。我們把代碼局部改寫如下:
//此靜態方法形參是傳入一個Pair<Number>的泛型實例 //static int add(Pair<Number> p){ //形參改為傳入一個Pair<? extends Number>的泛型實例, //Pair<? extends Number>使得方法接收所有泛型類型為Number或Number子類的Pair類型 static int add(Pair<? extends Number> p){ Number first = p.getFirst(); Number last = p.getLast(); //Number類型使用intValue()返回int類型 return first.intValue() + last.intValue(); }
這樣一來,給方法傳入Pair<Integer>
類型時,它符合參數Pair<? extends Number>
類型。這種使用<? extends Number>
的泛型定義稱之為上界通配符(Upper Bounds Wildcards),即把泛型類型T
的上界限定在Number
了。
除了可以傳入Pair<Integer>
類型,我們還可以傳入Pair<Double>
類型,Pair<BigDecimal>
類型等等,因為Double
和BigDecimal
都是Number
的子類。
如果我們考察對Pair<? extends Number>
類型調用getFirst()
方法,實際的方法簽名變成了:
<? extends Number> getFirst();
即返回值是Number
或Number
的子類,因此,可以安全賦值給Number
類型的變量:
Number x = p.getFirst();
然后,我們不可預測實際類型就是Integer
,例如,下面的代碼是無法通過編譯的:
Integer x = p.getFirst();
這是因為實際的返回類型可能是Integer
,也可能是Double
或者其他類型,編譯器只能確定類型一定是Number
的子類(包括Number
類型本身),但具體類型無法確定。
我們再來考察一下Pair<T>
的set
方法:
1 package day1_8; 2 3 public class Extends_Super { 4 public static void main(String[] args) { 5 Pair<Integer> p = new Pair<>(12,34); 6 System.out.println(add(p)); 7 } 8 static int add(Pair<? extends Number> p){ 9 Number first = p.getFirst(); 10 Number last = p.getLast(); 11 p.setFirst(new Integer(first.intValue()+100)); 12 p.setLast(new Integer(last.intValue() + 100)); 13 return p.getFirst().intValue() + p.getLast().intValue(); 14 } 15 } 16 17 class Pair<T> { 18 private T first; 19 private T last; 20 21 public Pair(T first, T last) { 22 this.first = first; 23 this.last = last; 24 } 25 26 public T getFirst() { 27 return first; 28 } 29 public T getLast() { 30 return last; 31 } 32 public void setFirst(T first) { 33 this.first = first; 34 } 35 public void setLast(T last) { 36 this.last = last; 37 } 38 }
不出意外,我們會得到一個編譯錯誤:
編譯錯誤發生在p.setFirst()
傳入的參數是Integer
類型。有些童鞋會問了,既然p
的定義是Pair<? extends Number>
,那么setFirst(? extends Number)
為什么不能傳入Integer
?
原因還在於擦拭法。如果我們傳入的p
是Pair<Double>
,顯然它滿足參數定義Pair<? extends Number>
,然而,Pair<Double>
的setFirst()
顯然無法接受Integer
類型。
這就是<? extends Number>
通配符的一個重要限制:方法參數簽名setFirst(? extends Number)
無法傳遞任何Number
的子類型給setFirst(? extends Number)
。
這里唯一的例外是可以給方法參數傳入null
:
p.setFirst(null); // ok, 但是后面會拋出NullPointerException p.getFirst().intValue(); // NullPointerException
extends通配符的作用
如果我們考察Java標准庫的java.util.List<T>
接口,它實現的是一個類似“可變數組”的列表,主要功能包括:
public interface List<T> { int size(); // 獲取個數 T get(int index); // 根據索引獲取指定元素 void add(T t); // 添加一個新元素 void remove(T t); // 刪除一個已有元素 }
現在,讓我們定義一個方法來處理列表的每個元素:
1 package day1_8; 2 3 import java.util.ArrayList; 4 import java.util.List; 5 6 public class Extends_Super { 7 public static void main(String[] args) { 8 ArrayList<Integer> list = new ArrayList<>(); 9 System.out.println(sumOfList(list)); 10 } 11 //注意形參處是List<Integer> 12 static int sumOfList(List<Integer> list){ 13 int sum = 0; 14 for (int i = 0; i < 101; i++) { 15 list.add(i); //可以對list寫 16 sum += list.get(i); //可以對list讀 17 } 18 return sum; 19 } 20 }
運行完全OK
接下來我們改動一處再看看
1 package day1_8; 2 import java.util.ArrayList; 3 import java.util.List; 4 5 public class Extends_Super { 6 public static void main(String[] args) { 7 ArrayList<Integer> list = new ArrayList<>(); 8 System.out.println(sumOfList(list)); 9 } 10 11 //注意形參改為<? extends Integer>通配符 12 static int sumOfList(List<? extends Integer> list){ 13 int sum = 0; 14 for (int i = 0; i < 101; i++) { 15 //下面兩行是一樣的,因為Integer和int是自動拆裝和包裝的 16 //list.add(i); //不能對list寫 17 list.add(new Integer(i)); //不能對list寫 18 sum += list.get(i); //可以對list讀 19 } 20 return sum; 21 } 22 }
為什么我們定義的方法參數類型是List<? extends Integer>
而不是List<Integer>
?從方法內部代碼看,傳入List<? extends Integer>
或者List<Integer>
是完全一樣的,但是,注意到List<? extends Integer>
的限制:
- 允許調用
get()
方法獲取Integer
的引用; - 不允許調用
set(? extends Integer)
方法並傳入任何Integer
的引用(null
除外)。
因此,方法參數類型List<? extends Integer>
表明了該方法內部只會讀取List
的元素,不會修改List
的元素(因為無法調用add(? extends Integer)
、remove(? extends Integer)
這些方法。換句話說,這是一個對參數List<? extends Integer>
進行只讀的方法(惡意調用set(null)
除外)。
使用extends限定T類型
在定義泛型類型Pair<T>
的時候,也可以使用extends
通配符來限定T
的類型:
public class Pair<T extends Number> { ... }
現在,我們只能定義:
Pair<Number> p1 = null; Pair<Integer> p2 = new Pair<>(1, 2); Pair<Double> p3 = null;
因為Number
、Integer
和Double
都符合<T extends Number>
。
非Number
類型將無法通過編譯:
Pair<String> p1 = null; // compile error! Pair<Object> p2 = null; // compile error!
因為String
、Object
都不符合<T extends Number>
,因為它們不是Number
類型或Number
的子類。
小結
使用類似<? extends Number>
通配符作為方法參數時表示:
-
方法內部可以調用獲取
Number
引用的方法,例如:Number n = obj.getFirst();
; -
方法內部無法調用傳入
Number
引用的方法(null
除外),例如:obj.setFirst(Number n);
。
即一句話總結:使用extends
通配符表示可以讀,不能寫。
使用類似<T extends Number>
定義泛型類時表示:
- 泛型類型限定為
Number
以及Number
的子類。
這里唯一的例外是可以給方法參數傳入null
: