Java泛型解析(02):通配符限定
阿新 • • 發佈:2017-07-31
target 程序 無法 pri 變量 div super 學習 return
Java泛型解析(02):通配符限定
考慮一個這種場景。計算數組中的最大元素。 [code01]
細致看看code01裏面的代碼(代碼不完整),使用類型參數T定義一個max局部變量,這幾意味著這個max能夠是隨意的類型。那麽max.compareTo(array[i]) 方法的調用的前提是T所屬的類中有compareTo方法,怎麽能做到這一點呢?別著急,讓我們來看看怎樣給類型參數進行限定,如今來對code01中的代碼進行完好。
[code02]
註意看,我們定義類型參數的變化:<T extends Comparable<T>>,這裏將T類型限定在Comparable及其全部的子類。是不是非常好奇Comparable明明是一個interfacce,依據所學知識推斷,實現interface用的keyword是implements,為什麽呢?
<T extends Bounding Type>,表示T類型應該是綁定類型及其子類型(subType),T和綁定類型能夠是類或者接口,使用extendskeyword由於它更接近於子類的概念,另外Java設計者並不打算為Java加入新的keyword如:sub
假設給T限定多個類型,則須要使用符號"&",如以下格式
[code03]
Java泛型解析(02):通配符限定
Java泛型解析(03):虛擬機運行泛型代碼 Java泛型解析(04):約束和局限性
=====【感謝親閱讀尋常心的文章,親若認為此文有幫助,頂一頂唄。親的支持將給我前進的動力】=====
考慮一個這種場景。計算數組中的最大元素。 [code01]
public class ArrayUtil { public static <T> T max(T[] array) { if (array == null || 0 == array.length) { return null ;} T max = array[0]; for (int i = 1; i < array.length; i++) { if (max.compareTo(array[i]) < 0) {max = array[i];} } return max; } }
public class ArrayUtil { public static <T extends Comparable<T>> T max(T[] array) { if (array == null || 0 == array.length) { return null ;} T max = array[0]; for (int i = 1; i < array.length; i++) { if (max.compareTo(array[i]) < 0) {max = array[i];} } return max; } }
<T extends Bounding Type>,表示T類型應該是綁定類型及其子類型(subType),T和綁定類型能夠是類或者接口,使用extendskeyword由於它更接近於子類的概念,另外Java設計者並不打算為Java加入新的keyword如:sub
<T extends Runnable & Serializable>細心的讀者可能會發現。這裏限定的都是interface。假設限定為class是不是也這麽自由的呢?先不急著回答這個問題。我們知道Java中能夠實現多個接口,而繼承僅僅能是單繼承,可想而知。當我們給T限定類型的時候,限定為某個class的時候是有限制的,看看以下幾組泛型限定的代碼 [code04]
<T extends Runnable & Serializable & ArrayList> // 錯誤 <T extends Runnable & ArrayList & Serializable> // 錯誤 <T extends ArrayList & LinkedList & Serializable> // 錯誤 <T extends ArrayList & Runnable& Serializable> // 正確不難看出,假設要限定T為class的時候,就有一個非常嚴格的規則,這個class僅僅能放在第一個。最多僅僅能有一個class。事實上非常easy理解,這樣一來。就行嚴格控制T類型是單繼承的,遵循Java的規範。 小結: 1.類型限定僅僅能限定某個類型及其子類。使用keywordextends。 2.多個類型參數用","隔開。如:<K, V>,多個限定類型用"&"隔開。如: <T extends Runnable & Serializable> 3.限定interface的時候。對interface的個數和順序無嚴格要求,限定class時。則須要將class類型置於第一個,且最多僅僅能存在一個class類型。 鉆牛角尖: 問:類型限定中能夠通過 extends 來限定子類型,能否夠通過類似superkeyword來限定超類型呢? 答:哈哈。問的好。接下來一一揭曉。 比較遺憾的是,類似<T extends Runnable & Serializable>這種泛型限定子類的語法。來限定超類是沒有成為Java中的一個語法規範的,比如: [code05]
<T super File & Runnable> // 錯誤code03中的類型參數的定義是錯誤的。至少眼下Java中沒有這種規範來支撐這種語法,怎樣解釋這個問題,筆者得花一番心思了... 不得不請教面向對象先生了: 1.面向接口(抽象)編程,而非面向實現編程。這個設計原則告訴我們。方法調用通過高層的抽象類或者接口來進行。詳細調用的方法體就是我們實際執行時期傳遞的詳細實現類的實例。這也是多態的一種體現。我們實現自己的泛型是提供後期應用程序猿使用的。限定一個子類,這就須要我們通過子類來調用方法,而調用的方法體則是這個類的超類的實例。繼承結構越往上就可能是abstract的。或者是interface。抽象類和接口是無法實例化對象。這樣的反設計讓調用面臨失敗。一旦限定的這個類就是抽象的或者是接口,必然會造成這個泛型類或泛型方法無法使用,導致設計失敗。舉個樣例: [code06]
public static <T super Runnable> void test(T runner) { runner.run(); }這個T類型限定為Runnable的超類。這個Runnable是一個接口。無法實例化對象,方法參數runner就是一個不存在的實例,所以這是一個失敗的設計。並且這樣的語法也無法通過編譯器。 面向對象先生的解釋對剛開始學習的人可能有點晦澀難懂。沒關系。這裏僅僅要知道Java是不能支持這樣的泛型限定的。
不管從設計角度,還是從後期擴展的角度。都是說只是去的。
可是不能這樣定義泛型。並不代表Java泛型中就沒有 super keyword了,接下來說說泛型中的通配符類型。有了前面的基礎。這裏恐怕不是問題了。 通配符類型 通配符類型,相比於固定的泛型類型,它是一個巧妙的解決方式。如: [code07]Couple<? extends Employee>表示Couple的泛型類型是Employee或者其子類,Couple<Manager>滿足。而Couple<File>不滿足了。通配符用 "?" 表示。 我們來打印一下夫婦類中的wife: [code08]
public static void printWife(Couple<Employee> couple) { Employee wife = couple.getWife(); System. out.println(wife); }code08中的方法參數僅僅能將Employee組成的夫婦傳入。貌似經理的如Couple<Manager>就不能了,這有點不合適了,搞得好像Manager還不能結婚了。
所以要想讓Manager也能結婚並打印其wife,須要更改我們的設計了:
[code09]public static void printWife(Couple<? extends Employee> couple) { Employee wife = couple.getWife(); System. out .println(wife); }通配符的子類型限定的語法與文章一開始介紹的類型限定有點相似,可是這裏有些細節的秘密。 [code10]
public static <T extends Comparable<T>> T max(T[] array) {...} public static void printWife(Couple<? extends Employee> couple) {...}前者T是提前定義的類型參數,T能夠作為一個詳細的類型來定義方法的參數類型,局部變量等,T的作用域是整個方法(方法返回值。參數,方法體中局部變量),這樣的設計是為了給使用者帶來方便,將參數類型的指定權有限制地交給了使用者。
而後者中不存在類型參數的定義,max方法參數的類型是預先定義好的Couple類型,使用者無法在使用的時候指定其它類型,但能夠有限制地指定Couple中的泛型參數的類型,
?extends Employee 自身不能單獨使用,能夠理解為僅僅能寄生在其它泛型類中,作為泛型類一個詳細的類型參數,通經常使用於定義階段,如以下:
[code11]public static ? extends Employee printWife() {...} // 錯誤 public static void printWife(? extends Empoyee employee) {...} // 錯誤使用通配符來定義方法返回值和方法的參數類型在Java中是不同意的! 弄清楚了前面類型限定和通配符的差別以後。再引入通配符的超類型限定就不是那麽難以理解了。 通配符的超類型限定: 和前面子類型的限定一樣,用"?"表示通配符,它的存在必須存在泛型類的類型參數中,如: [code12]
Couple<? super Manager>格式跟通配符限定子類型一樣。用了keywordsuper,可是這兩種方式的通配符存在一個隱蔽的差別,讓我們來揭曉吧,先看看以下代碼: [code13]、
Couple<Manager> couple = new Couple<Manager>(new Manager(),new Manager()); Couple<? extends Employee> couple2 = couple; Employee wife = couple2.getWife(); // couple2.setWife(new Manager()); // 此處編譯錯誤Couple<? extends Employee>定義了couple2後能夠將getWife和setWife想象成例如以下: [code14]
?getWife是能夠通過的,由於將一個返回值的引用賦給超類Employee是全然能夠的,而setWife方法接受的是一個Employee的子類。詳細是什麽子類,編譯器並不知道,拒絕傳遞不論什麽特定的類型。所以couple2.setWife(new Manager())是不能被調用的。extends Employee getWife() {...} void setWife(? extends Employee) {...}
所以通配符的子類限定適用於讀取。
在來看看通配符的超類型限定,即Couple<? super Manager>。getWife和setWife能夠想象成: [code15]? super Manager getWife() {...} void setWife(? super Manager) {...}getWife方法的返回值是Manager的超類型,而Manger的超類型是得不到保證的,虛擬器會將它會給Object,而setWife方法是須要的是Manager的超類型,所以傳入隨意Manager都是同意的,所以通配符的超類型限定適用於寫入。 無限定通配符 無限定通配符去除了超類型和子類型的規則。只用一個"?
"表示,而且也只能用於指定泛型類的類型參數中。如Couple<?>的形式,此時getWife和setWife方法如:
[code16]?getWife返回值直接付給了Object,而setWife方法是不同意調用的。那麽既然這麽脆弱,牛逼的Java設計者為什麽還要引入這樣的通配符呢?在一些簡單的操作中,五限定通配符還是實用武之地的。比方: [code17]getWife() {...} void setWife(?) {...}
public static boolean isCoupleComplete(Couple<?這種方法體中,getWife和getHusband返回值都是Object類型的,此時我們僅僅須要推斷是否為null就可以,而不須要知道詳細的類型是什麽,這就發揮了無限定通配符的作用了。發動腦經想一想。這種方法用文章開始所提到類型限定能否夠取代呢?自我思考中... [code18]> couple) { return couple.getWife() != null && couple.getHusband() != null; }
public static <T> boolean isCoupleComplete(Couple<T> couple) { return couple.getWife() != null && couple.getHusband() != null ; } public static <T extends Employee> boolean isCoupleComplete(Couple<T> couple) { return couple.getWife() != null && couple.getHusband() != null ; }到這裏,愛思考的讀者可能在思考一個問題,通配符代表了泛型類中的參數類型,在方法體中,怎麽去捕獲這個參數類型呢?
這裏考慮三種通配符的捕獲
1.Couple<?extends Employee> couple:getWife返回Employee
2.Couple<?super Manager> couple:無法捕獲,getWife返回Object
3.Couple<?> couple:無法捕獲,getWife返回Object
悲催了。僅僅有第一種能捕獲,怎麽辦呢?別著急,看看以下的小魔術: [code19]public static void print(Couple<?> couple) { printHelp(couple); } public static <T> void printHelp(Couple<T> couple) { T husband = couple.getHusband(); T wife = couple.getWife(); couple.setHusband(wife); couple.setWife(husband); System. out.println(husband); System. out.println(wife); }當須要捕獲通配符的時候,能夠借助前面所學的類型參數進行輔助。事實上這是一個多余的動作。基本上用不到這麽麻煩。這麽做是為了把通配符和泛型限定聯系起來,鞏固一下之前的學習。 總結: 1.泛型參數的限定,使用extendskeyword,限定多個類型時用"&"隔開。如:<T extends Runnable& Serializable> 2.泛型參數限定中,假設限定的類型是class而不是interface,則class必須放在限定類表中的第一個,且最多僅僅能存在一個class。如:<T extends ArrayList & Runnable& Serializable> 3.通配符僅僅能用在泛型類的泛型參數中,不能單獨使用。
如Couple<?
>、Couple<? extends Employee> 、Couple<? super Manager>
4.通配符的超類型限定適用於寫入,通配符的子類型限定適用於讀取,無限定通配符適用於一些非null推斷等簡單操作。 5.通配符的捕獲能夠借助泛型類型限定來輔助。 這一節內容比較多,須要花點時間好好消化,體會總結中的5點。下一節,說一個深刻點的話題,虛擬機中的針對泛型代碼的擦除。
Java泛型解析(02):通配符限定
Java泛型解析(03):虛擬機運行泛型代碼 Java泛型解析(04):約束和局限性
=====【感謝親閱讀尋常心的文章,親若認為此文有幫助,頂一頂唄。親的支持將給我前進的動力】=====
Java泛型解析(02):通配符限定