一、 為什么要使用泛型?
Java核心技術卷中寫到:“使用泛型機制撰寫的程式代碼要比那些雜亂的使用Object變數,然后在進行強制型別轉換的代碼具有更好的安全性和可讀性,泛型(Generic)意味著撰寫的代碼可以被很多的不同型別的物件重用”,
比如未使用泛型的ArrayList集合,只是維護一個Object型別陣列,在實作中沒有型別推斷,只能在運行時檢測錯誤,在獲取值時必須進行強制型別轉換,可能會出現ClassCastExcepyion例外,
@Test
public void test2(){
ArrayList a = new ArrayList();
a.add(132);
a.add(456);//沒有進行型別檢查,可以添加任意Object物件
a.add("Hello");
for (Object o :a) {
String str = (String) o;//進行強制型別轉換,編譯時不會報錯,運行時會出現java.lang.ClassCastException例外
System.out.println(str);
}
Java SE 5.0增加了泛型機制,使用泛型的Arrsylist類,有一個型別引數
@Test
public void test1(){
Collection<Integer> c = new ArrayList<Integer>();//型別推斷
c.add(12);
c.add(22);//能在編譯時而不是在運行時檢測錯誤
c.add(52);
//c.add("good"); 只能添加Integer類的物件
for (Integer o :c) { //不用進行強制型別轉換,編譯器已經知道o的資料型別,且運行時不會報錯
System.out.println(o);
}
}
二、 定義泛型類
“一個泛型類就是具有一個或多個型別變數的類”,如何實作?引入一個或多個型別變數,用< >括起來,并放在類名的后面,
作用是什么?類定義的類形變數可以指定類的內部結構的資料型別(比如屬性的資料型別,方法的回傳型別,方法的形參型別,成員變數的資料型別),
//開發一個泛型Apple類,要求有一個重量屬性weight在測驗類中實體化不同的泛型物件,
public class Apple<T> {
private T weight;//指定屬性的資料型別
public void setWeight(T weight){//指定方法的形參型別
this.weight = weight;
}
public T getWeight(){//指定方法的回傳值型別
return weight;
}
//泛型類定義構造器方式和一般類一樣
public Apple(){
}
//泛型類定義構造器方式和一般類一樣
public Apple(T weight){
this.weight = weight;
}
}
“用具體的型別替換型別變數就可以實體化泛型型別,”不能用基本資料型別指定型別變數,而要用其對應的包裝類,
@Test
public void test1(){
Apple<String> a1 = new Apple<>("500克");
Apple<Integer> a2 = new Apple<>(500);
Apple<Double> a3 = new Apple<>(500.0);
System.out.println(a1.getWeight());//回傳值型別是String型別
System.out.println(a2.getWeight());//回傳值型別是Integer型別
System.out.println(a3.getWeight());//回傳值型別是Double型別
}
}
三、 怎么定義泛型方法
什么是泛型方法?“帶有型別引數的方法”,泛型方法可以定義在普通類中,也可以定義在泛型類中,
型別變數放在權限修飾符的后面,回傳值型別的前面,
public class Arrayex {
public static <E> void toArray(E[] e, Collection<E> c){//型別變數修飾形參
for (E ex: e){//型別變數修飾區域變數
c.add(ex);
}
}
}
可以看出,泛型方法定義的型別變數可以定義方法的內部結構(形參,區域變數)以及方法的回傳值型別,
呼叫泛型方法?“呼叫時,在方法名的尖括號中放入具體的型別,也可以省略,編譯器有足夠的資訊推斷出所呼叫的方法,
@Test
public void test2(){
Collection<String> c1 = new ArrayList<>();
String[] str1 = new String[]{"C","Python","Java","C++"};
Arrayex.<String>toArray(str1,c1);//輸入第一個引數后,編譯器會型別推斷,指定第二個引數為容納String型別的Collection集合
System.out.println(c1);
}
}
顯然,在泛型類和泛型方法的定義和呼叫程序中,可以體驗到型別變數在類或方法的定義中充當的Object型別,當呼叫時指定具體傳入資料型別,幫助編譯器檢查傳入資料型別、型別推斷的作用,
四、型別變數的限定
“在類或方法中有時需要對型別變數加以約束”,對比Object類等型別指定的類或方法的結構,通常繼承的子類也能滿足代碼內部的使用,
“對于型別變數,可以指定為任何一個類的物件,假如內部呼叫了物件所屬類的compareTo方法,怎么才能確信T物件所屬類有CompareTo方法呢?解決方案是將T限定為實作了Comparable介面,
<T extends Compareable>
表示T是其實作了Comparable介面或者是系結型別的子型別,一個型別變數可以有多個限定,限定型別用“&”分隔,用逗號來分隔型別變數,
<T super xxxClass>
表示型別變數指定的范圍為xxxClass及其父類,
//回傳數組元素最大值
public static <E extends Emploee & Comparable> E max(E[] e){
if (e == null || e.length == 0) return null;
E maxplayer = e[0];
for (int i = 1; i < e.length; i++) {
if (maxplayer.compareTo(e[i])<0){
maxplayer = e[i];
}
}
return maxplayer;
}
在此方法中,型別變數限定為實作Comparable介面并且是Employee及其子類,
創建Person類、Employee類、Mannager類,三個類是多層繼承關系,Person重寫了compareTo方法對salary屬性,
@Test
public void test1(){
Emploee[] emploees = new Emploee[5];
emploees[0] = new Emploee("1號員工",12,100);
emploees[1] = new Emploee("2號員工",13,110);
emploees[2] = new Emploee("3號員工",13,120);
emploees[3] = new Manager("1號管理",12,100,50);
emploees[4] = new Manager("2號管理",14,130,50);
Object o = Emploee.max(emploees);//回傳工資最高的人
System.out.println(o);//名字 2號管理工資是 130
}
}
五、泛型變數與擦除
“無論何時定義一個泛型類,都自動提供一個相應的原始型別,類名就是洗掉型別引數后的泛型類名”,
擦除型別變數,原始型別會替換為第一個限定型別,如果沒有給定就用Object型別來替換,
“當程式呼叫泛型方法時,如果擦除回傳型別,編譯器會插入強制型別轉換,”
在虛擬機中沒有泛型,只有普通的類和方法,所有的型別引數都用它們的限定型別替換,橋方法被合成來保持多型(方法型別變數擦除時,呼叫來自父類的方法可能會變成另外一個父類方法,不在同名同引數產生多載,與多型沖突),
六、泛型的約束與局限性
運行時型別查詢只適用于原始型別, 使用instanceOf或涉及泛型型別的強制型別轉換運算式都會看到一個編譯器警告,
不能實體化泛型類引數化型別的陣列,
不能實體化泛型類的型別變數,
在靜態屬性或者靜態方法中不能使用泛型類的型別變數,
不能拋出或捕獲泛型類的實體,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/266215.html
標籤:Java
