成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

Java泛型總結(jié)

CoreDump / 488人閱讀

摘要:靜態(tài)變量是被泛型類的所有實(shí)例所共享的。對于這個類型系統(tǒng),有如下的一些規(guī)則相同類型參數(shù)的泛型類的關(guān)系取決于泛型類自身的繼承體系結(jié)構(gòu)。在代碼中避免泛型類和原始類型的混用。參考泛型類型擦除

Java泛型總結(jié)

Java泛型是JDK5引入的一個新特性,允許在定義類和接口的時候使用類型參數(shù)(type parameter)。聲明的類型參數(shù)在使用的時候使用具體的類型來替換。泛型最主要的應(yīng)用是在JDK5中的新集合類框架中。對于泛型概念的引入,開發(fā)社區(qū)的觀點(diǎn)是褒貶不一。從好的方面上說,泛型的引入可以解決之前的集合類框架在使用過程中通常會出現(xiàn)的運(yùn)行時刻類型錯誤,因?yàn)榫幾g器可以在編譯時刻就發(fā)現(xiàn)很多明顯的錯誤。從不好的方面說,為了保證與舊版本的兼容性,Java泛型的實(shí)現(xiàn)上還存在著不夠優(yōu)雅的地方。

類型擦除

正確理解泛型概念的首要前提是理解類型擦除(type erasure)。Java中的泛型基本上都是在編譯器這個層次來實(shí)現(xiàn)的。在生成的Java字節(jié)碼中是不包含泛型中的類型信息的。使用泛型的時候加上的類型參數(shù),會被編譯器在編譯的時候去掉。這個過程就稱為類型擦除。比如在代碼中定義的ListList等類型,在編譯之后都會變成List。JVM看到的只是List,而由泛型附加的類型信息對JVM來說是不可見的。Java編譯器會在編譯時盡可能的發(fā)現(xiàn)可能出錯的地方,但是仍然無法避免在運(yùn)行時刻出現(xiàn)類型轉(zhuǎn)換異常的情況。

通過如下代碼片段感受類型擦除:

        ArrayList a1 = new ArrayList<>();
        ArrayList a2 =new ArrayList<>();

        Class c1 =a1.getClass();
        Class c2 = a2.getClass();

        System.out.println(c1.equals(c2));    //Output: true

此時,程序輸出true,這就是類型擦除造成的。因?yàn)椴还苁?b>ArrayList還是ArrayList,都會在編譯期被編譯器擦除成ArrayList。編譯器這么做的原因歸根結(jié)底還是為了兼容JDK5前未使用泛型的代碼,因此不得不讓編譯器擦除有關(guān)類型信息的部分,這樣生成的代碼其實(shí)就是類型無關(guān)的。

        List list = new ArrayList<>();
        Map map = new HashMap<>();

        System.out.println(Arrays.toString(list.getClass().getTypeParameters()));    //[E]
        System.out.println(Arrays.toString(map.getClass().getTypeParameters()));    //[K, V]

我們期望的是返回泛型參數(shù)的類型,結(jié)果返回的僅僅是參數(shù)的占位符。

    public static  T[] makeArray(){
        return new T[10];    //編譯期報錯:不能創(chuàng)建泛型類型的數(shù)組
    }

因?yàn)?b>T僅僅是個占位符,并不具有真實(shí)的類型信息。為了解決這個問題,可以利用反射:

    public static  T[] makeArray(Class clazz) {
        return (T[]) Array.newInstance(clazz, 10);
    }

很多泛型的奇怪特性都與類型擦除的存在有關(guān),包括:

泛型類并沒有自己獨(dú)有的Class類對象。比如并不存在List.class或是List.class,而只有List.class。

靜態(tài)變量是被泛型類的所有實(shí)例所共享的。對于聲明為MyClass的類,訪問其中的靜態(tài)變量的方法仍然是MyClass.myStaticVar。不管是通過new MyClass還是new MyClass創(chuàng)建的對象,都是共享一個靜態(tài)變量。

泛型的類型參數(shù)不能用在Java異常處理的catch語句中。因?yàn)楫惓L幚硎怯?b>JVM在運(yùn)行時刻來進(jìn)行的。由于類型信息被擦除,JVM是無法區(qū)分兩個異常類型MyExceptionMyException的。對于JVM來說,它們都是MyException類型的,也就無法執(zhí)行與異常對應(yīng)的catch語句。

當(dāng)執(zhí)行類型擦除時,首先是找到用來替換類型參數(shù)的具體類。這個具體類一般是Object。如果指定了類型參數(shù)的上界的話,則使用這個上界。把代碼中的類型參數(shù)都替換成具體的類。同時去掉出現(xiàn)的類型聲明,即去掉<>的內(nèi)容。比如T get()方法聲明就變成了Object get();List就變成了List。接下來就可能需要生成一些橋接方法(bridge method),這是由于擦除了類型之后的類可能缺少某些必須的方法。比如考慮下面的代碼:

class MyString implements Comparable {
    public int compareTo(String str) {
        return 0;
    }
}

當(dāng)類型信息被擦除之后,上述類的聲明變成了class MyString implements Comparable。但是這樣類MyString就會有編譯錯誤,因?yàn)闆]有實(shí)現(xiàn)接口Comparable聲明的compareTo(Object)方法。這個時候就由編譯器來動態(tài)生成這個方法。

實(shí)例分析

了解類型擦除機(jī)制之后,就會明白編譯器承擔(dān)了全部的類型檢查工作。編譯器禁止某些泛型的使用方式,正是為了確保類型的安全性。以ListList為例來具體分析:

public void inspect(List list) {
    for(Object obj : list) {
        System.out.println(obj);
    }
    list.add(1);    //這個操作在當(dāng)前方法的上下文是合法的
}

public void test() {
    List strs = new ArrayList();
    inspect(strs);    //編譯錯誤
}

這段代碼中,inspect方法接受List作為參數(shù),當(dāng)在test方法中試圖傳入List的時候,會出現(xiàn)編譯錯誤。假設(shè)這樣的做法是允許的,那么在inspect方法中就可以通過list.add(1)來向集合中添加一個數(shù)字。這樣在test方法看來,其聲明為List的集合中被添加了一個Integer類型的對象,這顯然是違反類型安全原則的,在某個時候肯定會拋出ClassCastException。因此,編譯器禁止這樣的行為。

通配符與上下界

在使用泛型類的時候,既可以指定一個具體的類型,如List就聲明了具體的類型是String;也可以用通配符?來表示未知類型,如List就聲明了List中包含的元素類型是未知的。通配符所代表的其實(shí)是一組類型,但具體的類型是未知的。List所聲明的就是所有的類型都是可以的。但是List并不等同于List。List實(shí)際上確定了List中包含的是Object及其子類,在使用的時候可以通過Object來進(jìn)行引用。而List則表示其中所包含的元素類型是不確定。其中可能包含的是String,也可能是Integer。如果它包含了String的話,往里面添加Integer類型的元素就是錯誤的。正因?yàn)轭愋臀粗?,就不能通過new ArrayList()的方法來創(chuàng)建一個新的ArrayList對象。因?yàn)榫幾g器無法知道具體的類型是什么。但是對于List中的元素總是可以用Object來引用的,因?yàn)殡m然類型未知,但肯定是Object及其子類??紤]下面的代碼:

public void wildcard(List list) {
    list.add(1);    //編譯錯誤
}

如上所示,試圖對一個帶通配符的泛型類進(jìn)行操作的時候,總是會出現(xiàn)編譯錯誤。其原因在于通配符所表示的類型是未知的。

因?yàn)閷τ?b>List中的元素只能用Object來引用,在有些情況下不是很方便。在這些情況下,可以使用上下界來限制未知類型的范圍。如List說明List中包含的是Number及其子類。而List則說明List中包含的是Number及其父類。當(dāng)引入了上界時候,在使用類型的時候就可以使用上界類中定義的方法。比如訪問List的時候,就可以使用Number類的intValue等方法。

類型系統(tǒng)

Java中,比較常見的是通過繼承機(jī)制而產(chǎn)生的類型體系結(jié)構(gòu)。比如String繼承自Object。根據(jù)Liskov替換原則,子類是可以替換父類的。當(dāng)需要Object類的引用的時候,如果傳入一個String對象是沒有任何問題的。但是反過來的話,即用父類的引用替換子類引用時,就需要進(jìn)行強(qiáng)制類型轉(zhuǎn)換。編譯器并不能保證運(yùn)行時刻的這種轉(zhuǎn)換一定是合法的。這種自動的子類替換父類的轉(zhuǎn)換機(jī)制,對于數(shù)組也是適用的。String[]可以替換Object[]。但是泛型的引入,對于這個類型系統(tǒng)產(chǎn)生了一定的影響。例如List是不能替換List的。

引入泛型之后的類型系統(tǒng)增加了兩個維度:一個是類型參數(shù)自身的繼承體系結(jié)構(gòu),另外一個是泛型類或接口自身的繼承體系結(jié)構(gòu)。第一個指的是對于ListList這樣的情況,類型參數(shù)String繼承自Object。而第二種指的是List接口繼承自Collection接口。對于這個類型系統(tǒng),有如下的一些規(guī)則:

相同類型參數(shù)的泛型類的關(guān)系取決于泛型類自身的繼承體系結(jié)構(gòu)。即ListCollection的子類型,List可以替換Collection。這種情況也適用于帶有上下界的類型聲明。

當(dāng)泛型類的類型聲明中使用了通配符的時候,其子類可以在兩個維度上分別展開。如對Collection來說,其子類型可以在Collection這個維度上展開,即ListSet等;也可以在Number這個維度展開,即CollectionCollection等。如此循環(huán)下去,ArrayListHashSet等也都算是Collection的子類型。

如果泛型類中包含多個類型參數(shù),則對每個類型參數(shù)分別應(yīng)用上面的規(guī)則。

因此,對于上面錯誤的代碼,只需要將List修正為List即可。ListList的子類型。

開發(fā)自己的泛型類

泛型類與一般的Java類基本相同,只是在類和接口定義上多出來了用<>聲明的類型參數(shù)。一個類可以有多個類型參數(shù),比如MyClass。每個類型參數(shù)在聲明的時候可以指定上下界。所聲明的類型參數(shù)在Java類中可以像一般的類型一樣作為方法的參數(shù)和返回值,或是作為域和局部變量的類型。由于類型擦除機(jī)制,類型參數(shù)并不能用來創(chuàng)建對象或是作為靜態(tài)變量的類型??紤]下面的泛型類中的正確和錯誤的用法。

class ClassTest {

    private X x;
    private static Y y;    //編譯錯誤,不能用在靜態(tài)變量中
    
    public X getFirst() {
        return x;    //正確用法
    }

    public void wrong() {
        Z z = new Z();    //編譯錯誤,不能查創(chuàng)建對象
    }
}

假設(shè)允許類型參數(shù)聲明為靜態(tài)屬性,那么如下代碼將會非常混亂。

public class Computer {

    private static T os;

    public Computer(T os) {
        this.os = os;
    }

    public T getOS() {
        return os;
    }

    public static void main(String [] args) {
        Computer c1 = new Computer<>();
        Computer c2 = new Computer<>();
        Computer c3 = new Computer<>();

        System.out.println(c1.getOS());
        System.out.println(c2.getOS());
        System.out.println(c3.getOS());
    }
}

因?yàn)?b>os為Computer類的靜態(tài)屬性,所以c1,c2,c33Computer實(shí)例共享這個屬性,那么此時os的類型是什么?因此,不允許聲明靜態(tài)的類型參數(shù)屬性。

總結(jié)

在使用Java泛型的時候可以遵循一些基本的原則,從而避免一些常見的問題。

在代碼中避免泛型類和原始類型的混用。比如ListList不應(yīng)該共同使用。這樣會產(chǎn)生一些編譯器警告和潛在的運(yùn)行時異常。

在使用帶通配符的泛型類的時候,需要明確通配符所代表的一組類型的概念。由于具體的類型是未知的,很多操作是不允許的。

泛型類最好不要同數(shù)組一塊兒使用。只能創(chuàng)建new List[10]這樣的數(shù)組,無法創(chuàng)建new List[10]這樣的。這限制了數(shù)組的使用能力,而且會帶來很多費(fèi)解的問題。

參考

InfoQ

Java泛型:類型擦除

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/66092.html

相關(guān)文章

  • Java知識點(diǎn)總結(jié)Java泛型

    摘要:知識點(diǎn)總結(jié)泛型知識點(diǎn)總結(jié)泛型泛型泛型就是參數(shù)化類型適用于多種數(shù)據(jù)類型執(zhí)行相同的代碼泛型中的類型在使用時指定泛型歸根到底就是模版優(yōu)點(diǎn)使用泛型時,在實(shí)際使用之前類型就已經(jīng)確定了,不需要強(qiáng)制類型轉(zhuǎn)換。 Java知識點(diǎn)總結(jié)(Java泛型) @(Java知識點(diǎn)總結(jié))[Java, Java泛型] [toc] 泛型 泛型就是參數(shù)化類型 適用于多種數(shù)據(jù)類型執(zhí)行相同的代碼 泛型中的類型在使用時指定 泛...

    linkin 評論0 收藏0
  • Java 泛型總結(jié)(一):基本用法與類型擦除

    摘要:然而中的泛型使用了類型擦除,所以只是偽泛型。總結(jié)本文介紹了泛型的使用,以及類型擦除相關(guān)的問題。一般情況下泛型的使用比較簡單,但是某些情況下,尤其是自己編寫使用泛型的類或者方法時要注意類型擦除的問題。 簡介 Java 在 1.5 引入了泛型機(jī)制,泛型本質(zhì)是參數(shù)化類型,也就是說變量的類型是一個參數(shù),在使用時再指定為具體類型。泛型可以用于類、接口、方法,通過使用泛型可以使代碼更簡單、安全。然...

    Java_oldboy 評論0 收藏0
  • Java 泛型總結(jié)(二):泛型與數(shù)組

    摘要:總結(jié)數(shù)組與泛型的關(guān)系還是有點(diǎn)復(fù)雜的,中不允許直接創(chuàng)建泛型數(shù)組。本文分析了其中原因并且總結(jié)了一些創(chuàng)建泛型數(shù)組的方式。 簡介 上一篇文章介紹了泛型的基本用法以及類型擦除的問題,現(xiàn)在來看看泛型和數(shù)組的關(guān)系。數(shù)組相比于Java 類庫中的容器類是比較特殊的,主要體現(xiàn)在三個方面: 數(shù)組創(chuàng)建后大小便固定,但效率更高 數(shù)組能追蹤它內(nèi)部保存的元素的具體類型,插入的元素類型會在編譯期得到檢查 數(shù)組可以持...

    Vultr 評論0 收藏0
  • Java知識點(diǎn)總結(jié)(反射-反射操作泛型

    摘要:知識點(diǎn)總結(jié)反射反射操作泛型知識點(diǎn)總結(jié)反射采用泛型擦除的機(jī)制來引入泛型。中的泛型僅僅是給編譯器使用的,確保數(shù)據(jù)的安全性和免去強(qiáng)制類型轉(zhuǎn)換的麻煩。 Java知識點(diǎn)總結(jié)(反射-反射操作泛型) @(Java知識點(diǎn)總結(jié))[Java, 反射] Java采用泛型擦除的機(jī)制來引入泛型。Java中的泛型僅僅是給編譯器javac使用的, 確保數(shù)據(jù)的安全性和免去強(qiáng)制類型轉(zhuǎn)換的麻煩 。但是,__一旦編譯完成,...

    AprilJ 評論0 收藏0
  • Java 泛型總結(jié)(三):通配符的使用

    簡介 前兩篇文章介紹了泛型的基本用法、類型擦除以及泛型數(shù)組。在泛型的使用中,還有個重要的東西叫通配符,本文介紹通配符的使用。 這個系列的另外兩篇文章: Java 泛型總結(jié)(一):基本用法與類型擦除 Java 泛型總結(jié)(二):泛型與數(shù)組 數(shù)組的協(xié)變 在了解通配符之前,先來了解一下數(shù)組。Java 中的數(shù)組是協(xié)變的,什么意思?看下面的例子: class Fruit {} class Apple ex...

    itvincent 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<