1. 程式人生 > >Java泛型解析(02):通配符限定

Java泛型解析(02):通配符限定

target 程序 無法 pri 變量 div super 學習 return

Java泛型解析(02):通配符限定
考慮一個這種場景。計算數組中的最大元素。 [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;
          }
     }
細致看看code01裏面的代碼(代碼不完整),使用類型參數T定義一個max局部變量,這幾意味著這個max能夠是隨意的類型。那麽max.compareTo(array[i]) 方法的調用的前提是T所屬的類中有compareTo方法,怎麽能做到這一點呢?別著急,讓我們來看看怎樣給類型參數進行限定,如今來對code01中的代碼進行完好。

[code02]

    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 Comparable<T>>,這裏將T類型限定在Comparable及其全部的子類。是不是非常好奇Comparable明明是一個interfacce,依據所學知識推斷,實現interface用的keyword是implements,為什麽呢?
<T extends Bounding Type>,表示T類型應該是綁定類型及其子類型(subType),T和綁定類型能夠是類或者接口,使用extendskeyword由於它更接近於子類的概念,另外Java設計者並不打算為Java加入新的keyword如:sub
假設給T限定多個類型,則須要使用符號"&",如以下格式 [code03]
     <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]
     ?

extends Employee getWife() {...} void setWife(? extends Employee) {...}

getWife是能夠通過的,由於將一個返回值的引用賦給超類Employee是全然能夠的,而setWife方法接受的是一個Employee的子類。詳細是什麽子類,編譯器並不知道,拒絕傳遞不論什麽特定的類型。所以couple2.setWife(new Manager())是不能被調用的。

所以通配符的子類限定適用於讀取。

在來看看通配符的超類型限定,即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() {...} void setWife(?) {...}

getWife返回值直接付給了Object,而setWife方法是不同意調用的。那麽既然這麽脆弱,牛逼的Java設計者為什麽還要引入這樣的通配符呢?在一些簡單的操作中,五限定通配符還是實用武之地的。比方: [code17]
      public static boolean isCoupleComplete(Couple<?

> couple) { return couple.getWife() != null && couple.getHusband() != null; }

這種方法體中,getWife和getHusband返回值都是Object類型的,此時我們僅僅須要推斷是否為null就可以,而不須要知道詳細的類型是什麽,這就發揮了無限定通配符的作用了。發動腦經想一想。這種方法用文章開始所提到類型限定能否夠取代呢?自我思考中... [code18]
     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泛型解析(01):認識泛型
Java泛型解析(02):通配符限定
Java泛型解析(03):虛擬機運行泛型代碼 Java泛型解析(04):約束和局限性

=====【感謝親閱讀尋常心的文章,親若認為此文有幫助,頂一頂親的支持將給我前進的動力】=====

Java泛型解析(02):通配符限定