日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

泛型java博客园,Java深度历险之Java泛型

發(fā)布時間:2023/11/27 java 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 泛型java博客园,Java深度历险之Java泛型 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

Java泛型(generics)是JDK 5中引入的一個新特性,允許在定義類和接口的時候使用類型參數(shù)(type parameter)。聲明的類型參數(shù)在使用時用具體的類型來替換。泛型最主要的應(yīng)用是在JDK 5中的新集合類框架中。對于泛型概念的引入,開發(fā)社區(qū)的觀點是褒貶不一。

從好的方面來說,泛型的引入可以解決之前的集合類框架在使用過程中通常會出現(xiàn)的運行時刻類型錯誤,因為編譯器可以在編譯時刻就發(fā)現(xiàn)很多明顯的錯誤。而從不好的地方來說,為了保證與舊有版本的兼容性,Java泛型的實現(xiàn)上存在著一些不夠優(yōu)雅的地方。當然這也是任何有歷史的編程語言所需要承擔的歷史包袱。后續(xù)的版本更新會為早期的設(shè)計缺陷所累。

開發(fā)人員在使用泛型的時候,很容易根據(jù)自己的直覺而犯一些錯誤。比如一個方法如果接收ListObject作為形式參數(shù),那么如果嘗試將一個ListString的對象作為實際參數(shù)傳進去,卻發(fā)現(xiàn)無法通過編譯。雖然從直覺上來說,Object是String的父類,這種類型轉(zhuǎn)換應(yīng)該是合理的。但是實際上這會產(chǎn)生隱含的類型轉(zhuǎn)換問題,因此編譯器直接就禁止這樣的行為。本文試圖對Java泛型做一個概括性的說明。

類型擦除

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

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

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

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

泛型的類型參數(shù)不能用在Java異常處理的catch語句中。因為異常處理是由JVM在運行時刻來進行的。由于類型信息被擦除,JVM是無法區(qū)分兩個異常類型MyExceptionString和MyExceptionInteger的。對于JVM來說,它們都是 MyException類型的。也就無法執(zhí)行與異常對應(yīng)的catch語句。

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

classMyStringimplementsComparableString{publicintcompareTo(String str) {return0;

}

}

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

實例分析

了解了類型擦除機制之后,就會明白編譯器承擔了全部的類型檢查工作。編譯器禁止某些泛型的使用方式,正是為了確保類型的安全性。以上面提到的ListObject和ListString為例來具體分析:

publicvoidinspect(ListObjectlist) {for(Object obj : list) {

System.out.println(obj);

}

list.add(1);//這個操作在當前方法的上下文是合法的。}publicvoidtest() {

ListStringstrs=newArrayListString();

inspect(strs);//編譯錯誤}

這段代碼中,inspect方法接受ListObject作為參數(shù),當在test方法中試圖傳入ListString的時候,會出現(xiàn)編譯錯誤。假設(shè)這樣的做法是允許的,那么在inspect方法就可以通過list.add(1)來向集合中添加一個數(shù)字。這樣在test方法看來,其聲明為ListString的集合中卻被添加了一個Integer類型的對象。這顯然是違反類型安全的原則的,在某個時候肯定會拋出ClassCastException。因此,編譯器禁止這樣的行為。編譯器會盡可能的檢查可能存在的類型安全問題。對于確定是違反相關(guān)原則的地方,會給出編譯錯誤。當編譯器無法判斷類型的使用是否正確的時候,會給出警告信息。

通配符與上下界

在使用泛型類的時候,既可以指定一個具體的類型,如ListString就聲明了具體的類型是String;也可以用通配符?來表示未知類型,如List?就聲明了List中包含的元素類型是未知的。 通配符所代表的其實是一組類型,但具體的類型是未知的。List?所聲明的就是所有類型都是可以的。但是List?并不等同于ListObject。ListObject實際上確定了List中包含的是Object及其子類,在使用的時候都可以通過Object來進行引用。

而List?則其中所包含的元素類型是不確定。其中可能包含的是String,也可能是 Integer。如果它包含了String的話,往里面添加Integer類型的元素就是錯誤的。正因為類型未知,就不能通過new ArrayList?()的方法來創(chuàng)建一個新的ArrayList對象。因為編譯器無法知道具體的類型是什么。但是對于 List?中的元素確總是可以用Object來引用的,因為雖然類型未知,但肯定是Object及其子類。考慮下面的代碼:

publicvoidwildcard(List?list) {

list.add(1);//編譯錯誤}

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

因為對于List?中的元素只能用Object來引用,在有些情況下不是很方便。在這些情況下,可以使用上下界來限制未知類型的范圍。 如List? extends Number說明List中可能包含的元素類型是Number及其子類。而List? super Number則說明List中包含的是Number及其父類。當引入了上界之后,在使用類型的時候就可以使用上界類中定義的方法。比如訪問 List? extends Number的時候,就可以使用Number類的intValue等方法。

類型系統(tǒng)

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

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

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

當泛型類的類型聲明中使用了通配符的時候, 其子類型可以在兩個維度上分別展開。如對Collection? extends Number來說,其子類型可以在Collection這個維度上展開,即List? extends Number和Set? extends Number等;也可以在Number這個層次上展開,即CollectionDouble和 CollectionInteger等。如此循環(huán)下去,ArrayListLong和 HashSetDouble等也都算是Collection? extends Number的子類型。

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

理解了上面的規(guī)則之后,就可以很容易的修正實例分析中給出的代碼了。只需要把ListObject改成List?即可。ListString是List?的子類型,因此傳遞參數(shù)時不會發(fā)生錯誤。

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

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

classClassTestXextendsNumber, Y, Z{privateX x;privatestaticY y;//編譯錯誤,不能用在靜態(tài)變量中publicX getFirst() {//正確用法returnx;

}publicvoidwrong() {

Z z=newZ();//編譯錯誤,不能創(chuàng)建對象}

}

最佳實踐

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

在代碼中避免泛型類和原始類型的混用。比如ListString和List不應(yīng)該共同使用。這樣會產(chǎn)生一些編譯器警告和潛在的運行時異常。當需要利用JDK 5之前開發(fā)的遺留代碼,而不得不這么做時,也盡可能的隔離相關(guān)的代碼。

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

泛型類最好不要同數(shù)組一塊使用。你只能創(chuàng)建new List?[10]這樣的數(shù)組,無法創(chuàng)建new ListString[10]這樣的。這限制了數(shù)組的使用能力,而且會帶來很多費解的問題。因此,當需要類似數(shù)組的功能時候,使用集合類即可。

不要忽視編譯器給出的警告信息。

總結(jié)

以上是生活随笔為你收集整理的泛型java博客园,Java深度历险之Java泛型的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。

歡迎分享!

轉(zhuǎn)載請說明來源于"生活随笔",并保留原作者的名字。

本文地址:泛型java博客园,Java深度历险之Java泛型