1. 程式人生 > >java核心技術筆記——泛型

java核心技術筆記——泛型

1、泛型類:

public class Pair<T>{
    private T first;
    private T sceond;
    puiblic Pair(){first=null;second=null;}
    public T getFirst(){return first;}
    ...
    public void setFirst(T newValue){first=newValue}
    ...
}

當然也可以設計不同型別的泛型,public class Pair<T,U>{...},上述的都是定義泛型類,用具體的型別帶入進去就可以例項化泛型。比如Pair<String>,裡面的型別都將替換為具體String.

2、泛型方法:

class ArrayAlg{
    public static <T> T getMiddle(T...a){
        return a;
    }
}

泛型方法可以定義在泛型類中,也可以定義在普通的類中,注意型別變數<T>放在修飾符後面,返回型別的前面。有時候型別String的話可以省略,編譯器能夠推斷出。

3、型別變數的限定:

有時候可能對泛型變數做些條件限制,比如:

public static <T> T min(T[] a){

if(a[0].compareTo(a[1])).....

}

這時候就需要對T限制條件實現了Comparable介面的型別。即public static <T extends Comparable> T min(T[] a)。但是實際上comparable介面本身就是一個泛型。如果想實現多個限定,用&,即public static <T extends Comparable & Serializable> T min(T[] a).注意這裡用的是extends 而不是implements,用&而不是逗號。

4、泛型和虛擬機器。

a:型別擦除

定義一個泛型的類,會相應的生成一個原始型別,比如上面的泛型類的原始型別是

public class Pair{
    private Object first;
    private Object  sceond;
    puiblic Pair(){first=null;second=null;}
    public Object  getFirst(){return first;}
    ...
    public void setFirst(Object  newValue){first=newValue}
    ...
}

因為T沒有限定,所以就用Object替換為原始型別,如果有了限定,比如public class Pair<T extends Comparable & Serializable> {},則用Comparable替換為原始型別,如果切換限定,即T extends Serializable &Comparable的話,原始型別就會用Serializable替換,但是這樣的話,編譯器在必要時要向Comparable介面插入強制型別轉換,所以為了提高效率,一般將標籤介面(沒有方法的介面)放在後面。

泛型方法也會擦除:比如public static <T extends Comparable> T min(T[] a)擦除後為:public static  Comparable  min(Comparable  [] a)。

如果類繼承在擦除的時候繼承自父類擦除的方法和自己本類擦除後的方法會衝突,

class DateInterval extends Pair<LocalDate>{
    public void setSecond(LocalDate second){
        ....
    }
}
//擦除後
class DateInterval extends Pair{
    public void setSecond(LocalDate second){
        ....
    }
} 
//繼承的
public void setSecond(Object second)
//最終選用橋方法
public void ssetSecond(Object second){setSecond((Date) second)}

b:翻譯泛型表示式,

虛擬機器型別擦除後,返回的可能為原始型別,那麼程式呼叫泛型方法時,編譯器就會自動加入強制型別轉換,轉換為正確型別。比如呼叫Pair<Employee>的getFirst方法會返回Object型別,這時候編譯器會強制型別轉換為Employee型別。

對於翻譯泛型方法的時候如果遇到了衝突,編譯器會呼叫橋方法。不過橋方法可能會變得很奇怪,比如說DateInterval也覆蓋了getSecond方法,那麼在DateInterval中擦除後就會有兩個getsecond方法,LocalDate getsecond()和Object getsecond(),雖然再類中不可以出現這種情況,不過虛擬機器卻是可以正確處理。

總之:

虛擬機器中沒有泛型,只有普通的類和方法;所有的型別引數都用他們的限定型別替換;橋方法被合成保持多型;為了保持型別安全性,必要時插入強制型別轉換。

5、使用泛型的約束和侷限:

a、不能用基本型別例項化型別引數,因為型別擦除後,object不能儲存基本型別的值。

b、執行時型別查詢只適用於原始型別,比如a instanceof Pair<String> //error、a instanceof Pair<T> //error或者強制型別轉換Pair<String> p=(Pair<String>)a;//warning 同理getClass()返回的也是原始型別,所以Pair<String> a;Pair<Person> b,a.getClass()==b.getClass();

c、不能建立引數化型別的陣列,因為擦除後比如說Pair<String> table =new Pair<String>[10];//error 為Pair[]可以轉換為Object[],

Object a=table,a[0]="hello"//error(因為不是pair型別,無法通過陣列儲存檢查);a[0]=new Pair<Employee>();是可以通過陣列儲存檢查,但是最後還是會導致型別錯誤(即能儲存進去一個物件,但是呼叫pair類的方法的時候就會報型別轉換錯誤)。處於這個問題,還是不要件陣列,不過可以生明陣列,然後初始化原始型別。

d、varargs警告,這是針對引數個數可變的方法,其實引數可變是通過虛擬機器的處理實現的,比如說其實是建立了一個引數陣列,但是前面說過不能建立泛型陣列,不過這種情況特殊,編譯器會給出警告,這個警告可以通過@Suppress Warnings("unchecked")或者@SafeVarargs來消除警告。

e、不能例項化型別變數,不能使用象new T(..)或者new T[...]或者T.class。是因為型別擦除會將T替換成Object,編寫程式的本意肯定不是建立Object物件。解決辦法就是呼叫者提供一個構造器表示式例如:

Pair<String> p=Pair.makePair(String::new);

public static <T> Pair<T> makePair(Supplier<T> str){
//Supplier<T>是一個函式式介面,表示一個無引數而且返回型別為T的函式
    return new Pair<>(str.get(),str.get());
}

比較傳統的方式是用反射,t.class.newInstance();但是因為不能出現t.class,所以就要向下面這麼設計API

public static <T> Pair<T> makePair(Class<T> str){
    try{return new Pair<>(str.newInstance(),str.newInstance());}
    catch(Exception e){return null;}
}
//呼叫
Pair<String> p=Pair.makePair(String.class);

f、不能構造泛型陣列,因為擦出後構造的就不是想要的陣列型別,如果當陣列作為一個類的私有型別實力域的話就可以直接宣告為Object[]然後在獲取的時候加入強制型別轉換。但是如果方法的返回型別為T[],最好是提供一個數組構造器,比如String [] ss=ArrayAlg.minmiax(String::new,"Tom","Dick","Harry");其中構造器表示式String::new 指示一個函式,給定所需長度會構造一個指定長度的String陣列。也可以利用反射

public static <T extends Comparable> T[] minmax(T...a){
    T[] mm=(T[]) Array.newInstance(a.getClass.getComponentType(),2);
    ...
}

g、泛型類的靜態域或方法中的型別變數無效。即不能再靜態域或者方法中引用型別變數,比如定義private static T a;//error 或者在靜態方法中呼叫a都是錯誤的。

h、不能丟擲或者捕獲泛型類物件,實際上甚至泛型類擴充套件Throwable都是不合法。既不能public class Problem<T> extends Exception{}//error,catch中也不能引用型別變數當做引數,但是可以在catch字句中使用型別變數。即

catch(T e){..}//error
public static <T extends Throwable> void doWork(T t)Throws T{//ok
    ....
    catch(Throwable a){t.initCause(a);throw t;}
}

6、泛型與繼承

無論T和S什麼關係,Pair<T>和Pair<S>沒關係,但是Pair<T>是原始型別Pair的一個子型別,即Pair<manager> a=new Pair<>(cefo,cfo);Pair b=a;//ok 泛型類可以拓展或者實現其他泛型類,象普通類一樣。

7、萬用字元  ?

可以這樣定義Pair<? extends Employee>  那麼就可以實現Pair<Employee>或者Pair<Manager>,萬用字元定義的變數的方法也相當於萬用字元實現,比如Pair<? extends Employee>  a= new Pair(ceo,cfo);a 裡面的方法:?extends Employee getFirst();void setFirst(? extends Employee);

? super Manager  代表限制為所有Manager的超型別,

?無限制萬用字元,Pair<?> 裡會有 ?getFirst()和void setFirst(?) ,其中get只能將返回值賦給一個Object,set方法不能被呼叫。使用這個型別主要是為了普遍化一些簡單的操作。比如public static boolean hasNulls(Pair<?> p){return p.getFirst()==null||p.getSecond()==null;}

8、反射與泛型

反射中也可以應用到泛型,比如Class<T> 裡面有T newInstance(); T cast(Object obj);等等泛型方法。詳見java.lang.Class<T>,有時候也可以使用Class<T>引數型別進行匹配,比如public static <T> Pair<T> makePair(Class<T> c) throws InstantiationException{return new Pair<>(c.newInstance(),c.newInstance())}.如果呼叫nakePair(Employee.class)將返回一個Pair<Employee>,Employee.class是一個Class<Employee>的物件。