Technorati 标記: java,泛型,generic
java泛型應用是java核心基礎之一,從java 5開始引進泛型。如果你曾經使用過java Collection,那你已經算是接觸過泛型了。在java Collection裡使用泛型是一件很簡單的事情,可泛型還具有很多你意想不到的作用。在深入了解泛型之前,首先來了解一下泛型的一些基本概念與原理。
java泛型的應用可以提高的代碼的複用性,同時泛型提供了類型檢查,減少了資料的類型轉換,同時保證了類型安全。下面看一下,泛型如何保證了類型安全:
上面的代碼會在運作時抛出ClassCastException,因為它嘗試将一個Integer轉換為String。接着,來看一下從java5開始,Collection的用法:
注意到,List的建立增加了類型參數String,是以隻能向list添加String類型對象,添加其他對象會抛出編譯異常;同樣可以注意到,foreach循環不需要再添加任何強制類型轉換,也就移除了運作時的ClassCastException異常。
既然是學泛型,自然就要知道如何去使用泛型定義自己的類和接口。同時為了加深了解泛型的作用,先引進一個原始的類:
原始類的定義,容易引發ClassCastException,和第一大點談到的類似。現在來看一下泛型類來重新定義Gen — 使用<>指定泛型參數,如下:
細心的你會發現在main()方法裡是使用泛型類型Gen<String>,便不再需要強制類型轉換,也就移除了運作時的ClassCastException。同時為了差別,在此也定義了一個沒有使用泛型類型的gen2,這時,編譯器會彈出一個警告“Gen is a raw type,References to generic type Gen<T> should be parameterized”。當我們不提供泛型類型時,會預設使用Object會代替,也是是以這樣,gen2可以設定String和Integer類型,不過,我們應盡量去避免這種這種情況的出現,如此,便又需要用到強制類型轉換,也伴随着運作時的ClassCastException異常。
tips:可以使用@SuppressWarnings("rawtypes")來抑制編譯器彈出警告。
接口的泛型應用和類的泛型應用很類似,如下:
類似的,可以将此應用到自定義的接口與類當中。另外再提一下的是,可以使用多個泛型參數來定義接口與類,比如Map<K,V>;同時,泛型類型也可以作為一個參數來用,如下:new HashMap<String, List<String>>()。
為了更好地去了解泛型,我們也需要去了解java泛型的命名規範。為了與java關鍵字差別開來,java泛型參數隻是使用一個大寫字母來定義。各種常用泛型參數的意義如下:
E — Element,常用在java Collection裡,如:List<E>,Iterator<E>,Set<E>
K,V — Key,Value,代表Map的鍵值對
N — Number,數字
T — Type,類型,如String,Integer等等
S,U,V etc. - 2nd, 3rd, 4th 類型,和T的用法一樣
有時候我們并不希望整個類都被泛型化,這時可以隻在某個方法上應用泛型。因為構造函數是一種特殊的方法,是以也可以在構造函數上應用泛型。Demo GenMethod示範了如何在方法上應用泛型和調用泛型方法,
GenMethod 的代碼不多,不過需要注意的地方卻不少。第一、定義方法所用的泛型參數需要在修飾符之後添加,如上面的,public static <T>,如果有多個泛型參數,可如此定義<K,V>或者<T1,T2>。第二,不建議在泛型變量裡添加其他類型,如下面的代碼,将會引起編譯錯誤(或隐含錯誤),如下:
第三、看一下泛型方法的調用GenMethod.<Object>fromArrayToCollection(oa, co); 在方法前聲明了泛型類型Object。不過因為編譯器可以推斷這個泛型類型,是以也可以這樣寫:
GenMethod.fromArrayToCollection(oa, co)。
為了加深對編譯器推斷泛型類型的了解,再看一下如下幾個推斷:
有時候,你會希望泛型類型隻能是某一部分類型,比如操作資料的時候,你會希望是Number或其子類類型。這個想法其實就是給泛型參數添加一個界限。其定義形式為:
<T extends BoundingType>
此定義表示T應該是BoundingType的子類型(subtype)。T和BoundingType可以是類,也可以是接口。另外注意的是,此處的”extends“表示的子類型,不等同于繼承。
Demo:
通過Box<T>,了解了如何為泛型參數添加一個界限。可問題也來了,既然限定了泛型參數的界限,那時候可以調用BoundingType(上指Number)的相對應的方法呢??答案是肯定的,如下:
接着引入下一個問題,如何為泛型參數添加多個限制範圍,多重限制範圍格式如下:
<T extends A & B & C>
一個泛型參數可以有多重限制範圍,使用“&”分隔。且限制範圍中之多有一個類。如果用一個類作為限定,它必須是限定清單中的第一個。舉例如下:
如果BoundingType不是放在第一位,會産生編譯異常:
如果說泛型方法是一個有用的工具,那泛參的界限就應該這個工具的靈魂,為這個工具添加了一些“行為準則”。如下:設計一個方法,統計在一個數組裡比指定元素大的個數,
發現上面這個方法無法通過編譯,為什麼呢??因為操作符“>”隻可以用在基本資料類型(byte,char,short,int,float,long,double,boolean),卻不可以用來比較類對象之間的大小(除非實作了Comparable接口)。想要解決這個沖突,就需要為<T>添加一個界限Comparable<T>:
更改後的代碼如下:
除了上述方式,也可以選擇添加界限Comparator<T,T>,隻不過此界限需要兩個參數而已,Comparator的定義與使用以前已經談過,這裡不再累述,詳情可以點選 這裡。
如果兩個類之間互相相容(繼承與被繼承),那麼便可以将一個類對象指派給另一個類對象,比如:你可以将一個String對象指派給Object,String是Object的子類,
如果你熟悉面向對象技術,會知道這是一種“is-a”關系。String是Object的一種對象,是以上面的指派是可以的。同理,Integer、Double是Number的一類對象,下面的指派也可以:
這種“is-a”關系,同樣也是用泛型。如果你将泛參設定Number,那麼在随後的調用裡,隻需要傳入一個資料對象就行了,如下:
現在,考慮一下下面這種方法:
這個方法可以接受什麼類型的參數呢??顯然,這個方法接受Box<Number>類型的參數??那又是否可以接受Box<Integer>或者Box<Double>類型的參數的??答案是否定的,因為Box<Integer>與Box<Double>都不是Box<Number>的子類。在泛型程式設計裡,這是一個容易混淆的概念,但又必須要懂的原理。如下圖:

從圖可以看到,即使Integer是Number的子類,但Box<Integer>并不是Box<Number>的子類。Box<Integer>與Box<Number>的共同父類是Object。換言之,無論類A與類B是否存在關聯,MyClass<A>與MyClass<B>都沒有任何關聯,其共同的父類的是Object。那是否說,泛型就不存在子類呢??這個留待解決,看完本文便可以知曉。
在談這一小節時,先回顧一下泛型方法的“extends”含義,泛型的“extends”與繼承的“extends”并不一樣,泛型的“extends”其後可以是一個類(如T extends Number),同樣也可以是一個接口(如T extends List<T>)。泛型的”extends“代表子類型,而不是子類,或許你可以把等其同于”extends(繼承)“和”implement的并集。
在泛型裡,也存在子類型,前提是其泛型參數的限制并沒有改變,可以認為泛參沒有改變,其實就是從原來的類或接口來判斷泛型的子類型。為了形象了解,我們已collection類來作個例子,如:ArrayList<E> implement List<E>,而List<E> extends Collection<E>,那麼ArrayList<String>就是List<String>的子類型,而List<String>則是Collection<String>,其關系圖如下:
深入一點來談,現在假設需要定義自己的List接口 — PayLoadList,其定義如下:
如上,則下面的樣例都是List<String>子類型,: