標籤:類的方法 div pre ... list string 總結 error roc
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變數使用時,不要使用萬用字元
上述原則不試用與方法的傳回型別,不建議在傳回型別中使用萬用字元,否則將必須處理萬用字元的問題。
java基礎-泛型2