Java——泛型

  • 1 理解泛型

從Java5以後,Java引入了“參數類型化(parameterized type)”的概念,允許在創建集合時指定集合元素的類型。泛型很大程度上是爲了解決集合中存放元素類型的控制,從而可以保證程序如果在編譯時沒有發出警告,運行時就不會發生ClassCastException異常,並且從Java7之後有了泛型的“菱形”語法,更好的簡化了泛型的編程。

在一些資料中是這樣定義泛型的概念:所謂泛型,就是允許在定義類、接口、方法時使用類型參數,這個類型參數將在聲明變量、創建對象、調用方法時動態指定(即傳入實際的類型參數,類型實參),從而可以動態地生成無數多個邏輯上的子類,但這種子類在物理上並不存在。看下面一個簡單示例:

//在定義類時聲明類型形參,類型形參在整個勒種可當做類型使用
public class Person<T> {
    private T info;
    public Person(){}
    public Person(T info){
        this.info = info;
    }
    public T getInfo() {
        return info;
    }
    public void setInfo(T info) {
        this.info = info;
    }
    public static void main(String[] args){
        Person<String> strPerson = new Person<>("張三");
        Person<Double> doublePerson = new Person<>(30.0);
        System.out.println(strPerson.getClass()==doublePerson.getClass());
    }
}

在使用Person<T>時可以爲T類型形參傳入實際類型,這樣就可以生成如Person<String>、Person<Double>形式的邏輯上的子類,但物理上並不存在,從下圖來分析Java代碼在計算機中經歷的三個階段,不管爲泛型的類型形參傳入哪一種類型實參,對於Java來說它仍然被當成同一個類處理,在內存中也只佔用一塊內存空間,因此strPerson.getClass()==doublePerson.getClass()邏輯判斷結果爲true!!!(從以上幾句話也能夠知道在靜態方法、靜態初始化塊或者靜態變量的聲明和初始化中不允許使用類型形參

注:當創建帶泛型聲明的自定義類,爲該類定義構造器時,構造器還是原來的類名,不要增加泛型聲明。

  • 2.從泛型類派生子類

當創建了帶泛型聲明的接口、父類之後,可以爲該接口創建實現類,或者從該父類派生子類,需要指出的是,當使用這些接口、父類時不能再包含類型形參,必須制定類型實參。

public class Srudent extends Person<String> {}
//或者
public class Srudent extends Person {}
//錯誤寫法
public class Srudent extends Person<T> {}

在指定了具體的類型實參後,若需要重寫父類中使用了返回結果類型爲類型參數的方法,也必須要改爲具體實參類型:

public class Srudent extends Person<String> {
    @Override
    public String getInfo() {
        return super.getInfo();
    }
}
  • 3.通配符

考慮這樣一個工具類中的一個方法:遍歷List集合,打印集合中的元素對象。若直接在方法中寫List作爲方法參數類型,將引起泛型警告,可是在使用時,有可能每次調用該方法時傳入的List中的元素類型不同,在不引起泛型警告的情況下,該如何解決該問題??相信很多初識Java的人和我一樣,認爲答案是使用List<Object>,其實不對,List<String>等之類不能當做List<Object>對象使用。

public class Srudent extends Person<String> {
    public static void main(String[] args){
        List<String> strList = new ArrayList<>();
        //提示List<String>不能應用於List<Object>
        opList(strList);
    }
    public static void opList(List<Object> list){}
}

注意此類用法和數組的區別,若一個方法參數爲Number[],在調用時傳入Integer[]是可以的:

// 定義一個Integer數組
Integer[] ia = new Integer[5];
// 可以把一個Integer[]數組賦給Number[]變量
Number[] na = ia;
// 下面代碼編譯正常,但運行時會引發ArrayStoreException異常
// 因爲0.5並不是Integer
na[0] = 0.5;
List<Integer> iList = new ArrayList<>();
// 下面代碼導致編譯錯誤
List<Number> nList = iList;

能夠看出,在早期的Java設計之初,對於數組的設計安全缺陷是顯而易見的,因此在泛型中很好的避免了此類問題,如果A是B的一個子類型,C是具有泛型聲明的類或接口,G<A>並不是G<B>的子類型!!

回到我們的需求上,爲了能夠表示所有List的父類,可使用通配符(?),將問號作爲類型實參傳遞給List集合,意思是未知元素類型的List集合,它的元素類型可以匹配任何類型。另外,Java還提供了通配符限定,extends來限制通配符上限,super來限制通配符下限。在使用通配符的集合方法中,是不能向集合中執行添加對象的操作,因爲程序無法確定集合中元素的類型

注:該需求的另一個解決方式就是使用泛型方法

  • 4.泛型方法,泛型方法與通配符的區別

在定義類、接口時可以使用類型形參,在該類的方法和成員變量定義、接口的方法定義中,這些類型形參可被當成普通類型來使用。在另外一些情況下,定義類、接口時沒有使用類型形參,但定義方法時想自己定義類型形參,這種方法就是泛型方法,在調用泛型方法時要傳入具體的類型實參。定義語法格式如下

修飾符 <T,S> 返回值類型 方法名(形參列表){
    //方法體
}

回到在介紹通配符部分提出的需求,在此處可以使用泛型方法來解決

public class Srudent extends Person<String> {
    public static void main(String[] args){
        List<String> strList = new ArrayList<>();
        opList(strList);
    }
    public static <T> void opList(List<T> list){}
}

那麼,新的問題來了,何時使用通配符,何時使用泛型方法?一般可參考如下準則:

若類型形參只使用了一次,產生的唯一效果是可以在不同的調用點傳入不同類型的實際參數,對於這種情況應該使用通配符;泛型方法允許類型型參被用來表示方法的一個或多個參數之間的依賴關係,或者方法返回值與型參之間的依賴關係,應該使用泛型方法。如果某個方法中一個型參(a)的類型或返回值的類型依賴於另一個型參(b)的類型,則型參(b)的類型不應該使用通配符——因爲型參或返回值的類型依賴型參,如果型參(b)的類型不確定,程序就無法定義型參(a)的類型,在這種情況下,只能考慮使用在方法簽名中聲明類型型參,也就是泛型方法。

  • 5.泛型構造器

可以像聲明泛型方法那樣聲明構造器——在構造器簽名中聲明類型形參。

class Foo {
    public <T> Foo(T t) {
        System.out.println(t);
    }
}
public class GenericConstructor {
    public static void main(String[] args) {
        // 泛型構造器中的T參數爲String。
        new Foo("瘋狂Java講義");
        // 泛型構造器中的T參數爲Integer。
        new Foo(200);
        // 顯式指定泛型構造器中的T參數爲String,
        // 傳給Foo構造器的實參也是String對象,完全正確。
        new <String> Foo("瘋狂Android講義");
        // 顯式指定泛型構造器中的T參數爲String,
        // 但傳給Foo構造器的實參是Double對象,下面代碼出錯
        new <String> Foo(12.3);
    }
}

 

一直都想把泛型好好的學習一下,希望能夠系統的對它有個很好的認識,在觀看一些視頻和書籍之後,記錄筆記。

一點一滴的積累的量變,夯實質變的根基

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章