泛型
泛型是JDK5.0增加的新特性,泛型的本質是參數化類型,即所操作的數據類型被指定為一個參數。這種類型參數可以在類、接口、和方法的創建中,分別被稱為泛型類、泛型接口、泛型方法。
一、認識泛型
在沒有泛型之前,通過對類型Object的引用來實現參數的"任意化",但"任意化"帶來的缺點是需要顯示的強制類型轉換,此種轉換要求開發者對實際參數類型預知的情況下進行,對于強制轉換錯誤的情況,編譯器可能不會提示錯誤,但在運行時會出現異常,這是一個安全隱患。
舉例:不使用泛型實現參數化類型
1 package generic; 2 3 public class NoGeneric { 4 private Object ob; //定義通用類型成員 5 public NoGeneric(Object ob) { 6 this.ob = ob; 7 } 8 public Object getOb() { 9 return ob; 10 } 11 public void setOb(Object ob) { 12 this.ob = ob; 13 } 14 public void showType() { 15 System.out.println("實際類型是:"+ob.getClass().getName()); 16 } 17 } 18 19 package generic; 20 21 public class NoGenericDemo { 22 23 public static void main(String[] args) { 24 // TODO 自動生成的方法存根 25 //定義類NoGener的一個Integer版本 26 NoGeneric intob = new NoGeneric(new Integer(66)); 27 intob.showType(); 28 int i = (Integer)intob.getOb(); 29 System.out.println("value="+ i); 30 System.out.println("-----------------------------"); 31 //定義類NoGeneric的一個String版本 32 NoGeneric strob = new NoGeneric(new String("hello")); 33 strob.showType(); 34 String s = (String)strob.getOb(); 35 System.out.println("value="+ s); 36 } 37 }
? 執行結果為:
實際類型是:java.lang.Integer value=66 ----------------------------- 實際類型是:java.lang.String value=hello
?
?
? 上面的實例有兩點需要注意:首先如下語句
String s = (String)strob.getOb();
?
在使用時必須明確指定返回對象需要被強制轉化的類型為String,否則無法編譯通過;其次,由于intob和strob都屬于NoGeneric的類型,假如執行如下語句
intob = strob;
?
此種賦值,語法上是合法的,而在語義上是錯誤的,對于這種情況,只有在運行時才會出現異常,使用泛型就不會出現上述錯誤,泛型的好處就是在編譯期 檢查類型,捕捉類型不匹配錯誤,并且所有強制轉換都是自動和隱式的,提高代碼的重用率.
舉例 2:使用泛型使用泛型實現參數實例化類型
package generic;public class Generic<T> {private T ob; //定義泛型成員變量 public Generic(T ob) {this.ob = ob;}public T getOb() {return ob;}public void setOb(T ob) {this.ob = ob;}public void showType() {System.out.println("實例類型為:" + ob.getClass().getName());} }package generic;public class GenericDemo {public static void main(String[] args) {// TODO 自動生成的方法存根//定義泛型Generic的一個Integer的版本Generic<Integer> intob = new Generic<Integer>(88);intob.showType();int i = intob.getOb();System.out.println("value=" + i);System.out.println("----------------------");//定義泛型Generic的一個String版本Generic<String> strob = new Generic<String>("hello");strob.showType();String s = strob.getOb();System.out.println("value=" + s);} }
運行結果為:
實例類型為:java.lang.Integer value=88 ---------------------- 實例類型為:java.lang.String value=hello
?
在引入泛型的前提下,如果再次執行
intob = strob;
?
將提示錯誤,編譯無法通過
二、泛型定義
泛型的語法可歸納為
class class-name <type-param-list>{//......}
?
實例化泛型的語法為:
class-name <type-param-list> obj = new class-name<type-param-list>(cons-arg-list);
?
type-param-list用于指明當前泛型類可接受的類型參數占位符的個數; ? 如:
class Generic<T>{//......}
?
這里的T是類型參數的名稱,并且只允許傳一個類型參數給Generic類,在創建對象時,T用作傳遞 給Generic的實際類型的占位符,每當聲明類型參數時,只需用目標類型替換T即可. ? 如:
Generic <Integer> intob;
?
聲明對象時占位符T用于指定實際類型,如果傳遞實際類型為Integer,屬性ob就是Integer類型,類型T還可以指定方法的返回類型 ? ? 如:
public T getOb(){return ob; }
? 理解泛型有三點需要注意:
1、泛型的類型參數只能為類類型(包括自定義類),不能是基本數據類型。
2、同一種泛型可以對應多個版本(因為類型參數時不確定的)、不同版本的泛型類實例是不兼容的。
3、泛型的類型參數可以有多個。
注意 ? 根據慣例,泛型類定義時通常使用一個唯一的大寫字母表示一個類型參數.
三、有界類型
? 定義泛型類時,可以向類型參數指定任何類型信息,特別是集合框架操作中,可以最大限度地提高適用范圍,但有時候需要對類型參數的取值進行一定程度的限制,以使數據具有可操作性.
為了處理這種情況,java提供了有界類型,.在指定類型參數時可以使用extends關鍵字限制此類型參數代表的類必須是繼承自指定父類或父類本身.
使用extends關鍵字實現有界類型泛型類的定義
package generic;public class BoundGeneric<T extends Number> {//定義泛型數組 T[] array;public BoundGeneric(T[] array) {this.array = array;}//計算總和public double sum() {double sum = 0.0;for(T t : array) {sum = sum + t.doubleValue();}return sum;} }
?
BoundGeneric類的定義中,使用extends將T的類型限制為Number類及其子類,故可以再定義過程中調用Number類的doubleValue方法,現在分別指定Integer,double,String類型作為類型參數,測試BoundGeneric:
package generic;public class BoundGenericDemo {public static void main(String[] args) {// TODO 自動生成的方法存根//使用整形數組構造泛型對象Integer[] intArray = {1, 2, 3, 4};BoundGeneric<Integer> iobj = new BoundGeneric<Integer>(intArray);System.out.println("iobj的和為:" + iobj.sum());//使用Double型數組構造泛型對象Double[] douArray = {1.2, 2.3, 3.4, 4.5};BoundGeneric<Double> dobj = new BoundGeneric<Double>(douArray);System.out.println("dobj的和為:" + dobj.sum());String[] strArray = {"str1","str2"};//下面的語句將會報錯,String不是Number的子類//BoundGeneric<String> sobj = new BoundGeneric<String>(strArray); } }
?
運行結果為:
iobj的和為:10.0
dobj的和為:11.4
注:在使用extends(如:T extends someClass)聲明的泛型類進行實例化時允許傳遞的參數類型為:如果someClass是類,可以傳遞someClass本身及其子類;如果someClass接口可以傳遞實現接口的類
四、通配符
首先在說通配符之前先看一下這段代碼:使用前面定義的Generic類.
package generic;public class WildcarDemo {public static void func(Generic <Object> g) {//... }public static void main(String args[]) {Generic <Object> obj = new Generic<Object>(12);func(obj);Generic<Integer> iobj = new Generic<Integer>(12);//這里講產生一個錯誤:類型 WildcarDemo 中的方法 func(Generic<Object>)對于參數(Generic<Integer>)不適用//func(iobj); } }
?
上述代碼的func()方法的創建意圖是能夠處理各種類型參數的Generic對象,因為Generic是泛型,所以在使用時需要為其指定具體的參數化類型Object,看似不成問題,
但在
func(iobj);
?
處產生一個編譯錯誤,因為func定義過程中以明確聲明的Generic的類型參數為Object,這里試圖將Generic<Integer>類型的對象傳遞給func()方法,類型不匹配導致編譯錯誤.這種情況可以使用通配符解決.通配符由"?"來表示,它代表一個未知類型
package generic;public class WildcarDemo2 {public static void func(Generic <?> g) {//... }public static void main(String args[]) {Generic<Object> obj = new Generic<Object>(12);func(obj);Generic<Integer> iobj = new Generic<Integer>(12);func(iobj);} }
?
上述代碼,在采用了通配符后語句將無誤的編譯,運行.
在通配符使用的過程中,也可通過extends關鍵字限定通配符的界定的類型參數的范圍.
package generic;public class WildcarDemo3 {public static void func(Generic <? extends Number> g) {//... }public static void main(String args[]) {Generic<Object> obj = new Generic<Object>(12);//這里將產生一個錯誤:類型 WildcarDemo3 中的方法 func(Generic<? extends Number>)對于參數(Generic<Object>)不適用//func(obj); Generic<Integer> iobj = new Generic<Integer>(12);func(iobj);} }
?
五、泛型的局限性
java并沒有真正實現泛型,是編譯器在編譯的時候在字節碼上做了手腳(稱為擦除). 這種實現理念在成java泛型本身有很多漏洞, 為了避免這些問題java對泛型的使用上做了一些約束,但不可避免的還是有一些問題存在.多數的限制都是由類型擦除引起的.
1、泛型類型不能被實例化
public class Gen<T>{T ob;public Gen(){ob = new T();} }
?
Gen<T>構造器是非法的,類型擦除將變量T替換成Object,但這段代碼的本意肯定不是調用new Object().類似:如
public <T> T[]build (T[] a){T [] array = new T[2];
//... }
?
類型擦除會讓這個方法總是構造一個Object[2]數組,但是可以通過調用Class.newInstance和Array.newInstance方法,利用反射構造泛型對象和數組
2、數組
不能實例化數組如:
T[] vals; vals = new T[10];
?
因為T在運行時時不存在的,編譯器無法知道實際創建那種類型的數據.
其次,不能創建一個類型特定的泛型引用的數組 ? ?如:
Gen<String> []arrays = new Gen<String>[100];
?
上面的代碼會損害類型安全
如果使用通配符,就可以創建泛型類型的引用數組
Gen<?> []arrays = new Gen<?>[10];
?
3、怒能用類型參數替換基本類型
因為擦除類型后原先的類型參數被Object或者限定類型替換,而基本類型是不能被對象所存儲的,可以使用基本類型的包裝類來解決此問題
4、異常
不能拋出也不能捕獲泛型類的異常對象,使用泛型類來擴展Throwable也是非法的. 如:
public class GenericException <T> extends Exception{//泛型類無法繼承Throwable }
?
不能再catch子句中使用類型參數,例如下面的方法將不能編譯
public static <T extends Throwable> void doWork(Class<T> t) {try {//...}catch(Throwable realCause) {//...}
?
但是在異常聲明時可以使用類型參數,如:
public static <T extends Throwable> void doWork(T t) throws T {try {//...}catch(Throwable realCause) {throw t;}}
?
5、靜態成員
不能在靜態變量或者靜態方法中引用類型參數 ?如:
public class Gen<T>{static T ob;static T getOb() {return ob;} }
?這些均參考自“Java SE程序設計”,算是做個筆記,以后忘了可以翻閱一下,寫在自己的隨筆中,也希望可以幫助更多的人。如有侵權,請聯系本人刪除
?