6 類型推測 java編譯器能夠檢查所有的方法調用和對應的聲明來決定類型的實參,即類型推測,類型的推測演算法推測滿足所有參數的最具體類型,如下例所示: 6.1 泛型方法的類型推測 類型的推測可以使泛型方法的使用語法和普通的方法一樣,不必指定尖括弧內的類型,如上述例子。 6.2 泛型類的類型推測 對於泛 ...
6 類型推測
java編譯器能夠檢查所有的方法調用和對應的聲明來決定類型的實參,即類型推測,類型的推測演算法推測滿足所有參數的最具體類型,如下例所示:
//泛型方法的聲明 static <T> T pick(T a1, T a2) { return a2; } //調用該方法,根據賦值對象的類型,推測泛型方法的類型參數為Serializable //String和ArrayList<T>都實現介面Serializable,後者是最具體的類型 Serializable s = pick("d", new ArrayList<String>());
6.1 泛型方法的類型推測
類型的推測可以使泛型方法的使用語法和普通的方法一樣,不必指定尖括弧內的類型,如上述例子。
6.2 泛型類的類型推測
對於泛型類的使用,java編譯器也可以進行類型的推測,因此調用泛型類時,可以不用指定尖括弧內的類型參數,不過尖括弧不可省略,之前的總結已經提到,空的尖括弧又叫鑽石(中文怪怪的),如下例所示:
//以下用法沒有指定類型參數,尖括弧為空 Map<String, List<String>> myMap = new HashMap<>(); //註意,空的簡括弧不能省略,如下代碼編譯器會發出警告 Map<String, List<String>> myMap = new HashMap();
上述代碼中的第二個賦值語句中new HashMap() 實際是用的原始類型。
6.3 非泛型類的泛型構造器的類型參數推測
無論是泛型還是非泛型的類都可以使用泛型的構造器,如方法一樣。
//類定義 class MyClass<X> { <T> MyClass(T t) { // ... } } //以下是實例化以上類的表達式 new MyClass<Integer>("")
以上代碼中的實例化表達式雖然沒有指定構造器的類型參數,但是可以根據傳入的參數推測其類型參數為String。
java7以前的版本能夠推測出構造其的參數類型,而java7以後,使用鑽石的語法也推測泛型類的參數類型。
需要註意的是,類型參數的推測演算法只會使用傳入的參數,目的類型或者和明顯的返回類型來推測類型。
6.4 目的類型
java編譯器充分利用了目的類型來推測泛型方法或者類的類型參數,如下例:
//Collections中的一個方法的聲明如下 static <T> List<T> emptyList(); //現在調用該方法 List<String> listOne = Collections.emptyList();
以上中的第二個語句中,listOne變數類型為List<string>,就是目的類型,所以需要方法emptyList的返回類型也必須是List<Stirng>,這樣可以推測泛型方法聲明中的T為String,java7和8都可以實現這樣的推測,當然你可以在調用泛型方法時指明方括弧中的類型參數。
值得註意的是java7中方法的參數還不屬於目的類型,而java8則把方法參數加入目的類型,如下例所示:
//如下方法接受的參數為List<String> void processStringList(List<String> stringList) { // process stringList } //Collections中的emptyList方法的簽名如下 static <T> List<T> emptyList(); //java7中,下列調用語句的編譯會報錯,而java8則不存在這樣的問題 processStringList(Collections.emptyList());
7 通配符
在泛型的代碼中問號(?)代表通配符,代表未知的類型,通配符可以用在許多場合,可用作參數,欄位,返回值的類型,但是通配符不能用作方法調用,泛型實例的創建和父類型的實參。
7.1 上限通配符
利用上限通配符可以放鬆對變數的限制。
上限通配符的聲明方法如下例所示:
public static void process(List<? extends Foo> list) { /* ... */ }
上述聲明的方法,的泛型參數使用了上限通配符,通配符"?"加extends關鍵詞後跟其上限,此處的extends類似於通常意義上的extends和implements,意思是該方法是針對於Number類型的子類型,包括Integer,Float等的列表。
通配符<? extends Foo>匹配所有的Foo的子類型和Foo類型自身。
7.2 無限制通配符
無限制通配符就是簡單的"?",如List<?>就代表未知類型的列表,以下兩種情況適合使用無限制通配符:
- 聲明一個要用到繼承的Object類中的方法時
- 當代碼中需要用到不依賴於類型參數的泛型類的方法時, 如List.size或者List.clear,而Class<?> 經常被用到,因為Class<T>中的許多方法是不依賴於類型參數T的。
以下例子很好的說明使用Object類中的方法時使用無限制通配符的好處:
//普通的方法聲明 public static void printList(List<Object> list) { for (Object elem : list) System.out.println(elem + " "); System.out.println(); } //使用通配符的泛型作為方法參數,該方法的參數能夠傳入任何類型的列表(List) public static void printList(List<?> list) { for (Object elem: list) System.out.print(elem + " "); System.out.println(); }
註意:既然定義了列表List<?>的類型的廣泛性,就要承擔廣泛性的造成的後果,在方法聲明中,只能對List<?>類型的變數插入null,因為你無法預知傳入方法的類型變數,而List<Object>作為參數則可以插入任何類型的對象。
7.3 下限通配符
與上限通配符類似,下限通配符指定了類型參數的下限,未知的類型必須是指定類型的父類型,下限通配符的寫法:<? super A>,此處關鍵詞為super。
註意:不能同時指定上限和下限。
7.4 通配符和子類型
之前提到過,泛型之間的關係不僅僅是由他們的類型實參決定的,如不能說List<Number>就是List<Integer>的父類,不過使用通配符可以構成如下關係:
箭頭表示“是其子類型”的關係,如List<Integer>是List<? extends Integer>的子類型,可以這樣理解:List<Integer>是一種List<? extends Integer>。
7.5 通配符的捕獲與輔助方法
有時候編譯器會推測通配符的類型,如果一個欄位的類型被定義為List<?>,當運算一個表達式的時候,編譯器會從代碼中推測該欄位為一個特定的類型,這就叫通配符的捕獲。
import java.util.List; public class WildcardError { void foo(List<?> i) { i.set(0, i.get(0)); } }
上述代碼會編譯出錯,foo方法調用List.set(int,E),編譯器首先將set方法內作為參數的i視為Object類型,無法判斷將要插入的對象類型是否和目標列表類型是否一致,所以編譯不能通過。
此時可以加入一個輔助方法,使其能能夠順利通過編譯:
public class WildcardFixed { void foo(List<?> i) { fooHelper(i); } // 創建輔助方法,調用該方法可以通過類型推測來實現通配符的捕獲 private <T> void fooHelper(List<T> l) { l.set(0, l.get(0)); } }
再來看一下一個例子:
import java.util.List; public class WildcardErrorBad { void swapFirst(List<? extends Number> l1, List<? extends Number> l2) { Number temp = l1.get(0); l1.set(0, l2.get(0)); l2.set(0, temp); } }
上述代碼中的方法功能是將兩個列表的首個元素交換,然而無法判斷兩個傳入的實參的類型參數是否相容,所以,無法編譯通過,此處代碼本質上就是錯誤的,沒有相應的輔助方法。
7.6 通配符使用原則
泛型的使用有一點讓人疑惑的就是不知道什麼時候該用上限通配符,什麼時候使用下限通配符,一下是幾點原則:
為了說明問題,先列出兩種變數1)In變數:作為代碼中的數據來源,比如複製的方法copy(src,dest)中的src參數就是in變數,;2)out變數,在代碼中用來存儲數據作為他用,如copy(src,dest)中的dest參數就是out變數。變數列出之後,說原則:
- in變數使用上限通配符,使用extends關鍵詞
- out變數使用下限通配符,使用super關鍵詞
- 當需要使用的in變數可以通過Object類中的方法訪問時,使用無限制通配符
- 當代碼中既需要訪問的變數既要當做in變數使用,又要當做out變數使用時,不要使用通配符
上述原則不試用與方法的返回類型,不建議在返回類型中使用通配符,否則將必須處理通配符的問題。