Java中的泛型_Effective Java 2.0_Item 1知識點__java

來源:互聯網
上載者:User
文章作者:Tyan

部落格:noahsnail.com 1. 什麼是泛型

Java泛型(Generics)是JDK 5中引入的一個新特性,允許在定義類和介面的時候使用型別參數(type parameter),它們也被稱為參數化型別(parameterized type)或參量多態(parametric polymorphism)。泛型最主要的應用是在JDK 5中的新集合類架構中。Java泛型的應用可以提高代碼的複用性,同時泛型提供了類型檢查,減少了資料的類型轉換,保證了編譯時間的型別安全。 1.1 Java泛型的優點

Java泛型保持了和Java語言以及Java虛擬機器很好的相容性,下面對Java泛型的特點做一個簡要的概:

型別安全。 泛型的一個主要目標就是提高Java程式的型別安全。使用泛型可以使編譯器知道變數的類型限制,進而可以在更高程度上驗證類型假設。如果沒有泛型,那麼類型的安全性主要由程式員來把握,這顯然不如帶有泛型的程式安全性高。

消除強制類型轉換。泛型可以消除原始碼中的許多強制類型轉換,這樣可以使代碼更加可讀,並減少出錯的機會。

向後相容。支援泛型的Java編譯器(例如JDK5.0中的Javac)可以用來編譯經過泛型擴充的Java程式(Java泛型程式),但是現有的沒有使用泛型擴充的Java程式仍然可以用這些編譯器來編譯。

層次清晰,恪守規範。無論被編譯的來源程式是否使用泛型擴充,編譯產生的位元組碼均可被虛擬機器接受並執行。也就是說不管編譯器的輸入是Java泛型程式,還是一般的Java程式,經過編譯後的位元組碼都嚴格遵循《Java虛擬機器規範》中對位元組碼的要求。可見,泛型主要是在編譯器層面實現的,它對於 JAVA 虛擬機器是透明的。

效能收益。目前來講,用Java泛型編寫的代碼和一般的Java代碼在效率上是非常接近的。 但是由於泛型會給Java編譯器和虛擬機器帶來更多的類型資訊,因此利用這些資訊對Java程式做進一步最佳化將成為可能。 1.2 使用Java泛型要注意的事項

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

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

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

泛型類最好不要同數組一塊使用。你只能建立new List 2. 與泛型相關的一些概念 2.1 類型擦除

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

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

泛型類並沒有自己專屬的Class類對象。比如並不存在List.class或是List.class,而只有List.class。

靜態變數是被泛型類的所有執行個體所共用的。對於聲明為MyClass的類,訪問其中的靜態變數的方法仍然是 MyClass.myStaticVar。不管是通過new MyClass還是new MyClass建立的對象,都是共用一個靜態變數。

泛型的型別參數不能用在Java異常處理的catch語句中。因為異常處理是由JVM在運行時刻來進行的。由於類型資訊被擦除,JVM是無法區分兩個異常類型MyException和MyException的。對於JVM來說,它們都是 MyException類型的。也就無法執行與異常對應的catch語句。

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

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

當類型資訊被擦除之後,上述類的聲明變成了class MyString implements Comparable。但是這樣的話,類MyString就會有編譯錯誤,因為沒有實現介面Comparable聲明的int compareTo(Object)方法。這個時候就由編譯器來動態產生這個方法。

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

public void inspect(List<Object> list) {        for (Object obj : list) {                System.out.println(obj);        }        list.add(1); //這個操作在當前方法的上下文是合法的。 }public void test() {        List<String> strs = new ArrayList<String>();        inspect(strs); //編譯錯誤 }  

這段代碼中,inspect方法接受List作為參數,當在test方法中試圖傳入List的時候,會出現編譯錯誤。假設這樣的做法是允許的,那麼在inspect方法就可以通過list.add(1)來向集合中添加一個數字。這樣在test方法看來,其聲明為List的集合中卻被添加了一個Integer類型的對象。這顯然是違反型別安全的原則的,在某個時候肯定會拋出ClassCastException。因此,編譯器禁止這樣的行為。編譯器會儘可能的檢查可能存在的型別安全問題。對於確定是違反相關原則的地方,會給出編譯錯誤。當編譯器無法判斷類型的使用是否正確的時候,會給出警告資訊。 2.2 萬用字元與上下界

在使用泛型類的時候,既可以指定一個具體的類型,如List就聲明了具體的類型是String;也可以用萬用字元?來表示未知類型,如List

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

如上所示,試圖對一個帶萬用字元的泛型類進行操作的時候,總是會出現編譯錯誤。其原因在於萬用字元所表示的類型是未知的。

因為對於List 2.3 類型系統

在Java中,大家比較熟悉的是通過繼承機制而產生的類型體繫結構。比如String繼承自Object。根據Liskov替換原則,子類是可以替換父類的。當需要Object類的引用的時候,如果傳入一個String對象是沒有任何問題的。但是反過來的話,即用父類的引用替換子類引用的時候,就需要進行強制類型轉換。編譯器並不能保證運行時刻這種轉換一定是合法的。這種自動的子類替換父類的類型轉換機制,對於數組也是適用的。 String[]可以替換Object[]。但是泛型的引入,對於這個類型系統產生了一定的影響。正如前面提到的List是不能替換掉List的。

引入泛型之後的類型系統增加了兩個維度:一個是型別參數自身的繼承體繫結構,另外一個是泛型類或介面自身的繼承體繫結構。第一個指的是對於 List和List這樣的情況,型別參數String是繼承自Object的。而第二種指的是 List介面繼承自Collection介面。對於這個類型系統,有如下的一些規則:

相同型別參數的泛型類的關係取決於泛型類自身的繼承體繫結構。即List是Collection 的子類型,List可以替換Collection。這種情況也適用於帶有上下界的型別宣告。

當泛型類的型別宣告中使用了萬用字元的時候,其子類型可以在兩個維度上分別展開。如對Collection 2.4 橋方法

http://www.cnblogs.com/ggjucheng/p/3352519.html 3. 泛型類

一個典型的泛型類的例子是ArrayList類:

public class ArrayList<E> extends AbstractList<E>        implements List<E>, RandomAccess, Cloneable, java.io.Serializable {}

用法就不用多說了。 4. 泛型介面

一個典型的泛型介面例子是Comparable介面:

public interface Comparable<T> {    ...    public int compareTo(T o);    ...}

用法:

public class Test implements Comparable<String>{    @Override    public int compareTo(String o) {        return 0;    }}
5. 泛型方法
public class Util {    public static <K, V> boolean compare(Pair<K, V> p1, Pair<K, V> p2) {        return p1.getKey().equals(p2.getKey()) &&               p1.getValue().equals(p2.getValue());    }}
6. 泛型命名規範

為了更好地去理解泛型,我們也需要去理解Java泛型的命名規範。為了與Java關鍵字區別開來,Java泛型參數只是使用一個大寫字母來定義。各種常用泛型參數的意義如下:

E — Element,常用在Java Collection裡,如:List,Iterator,Set

K,V — Key,Value,代表Map的索引值對

N — Number,數字

T — Type,類型,如String,Integer等等

參考資料:

1、http://www.infoq.com/cn/articles/cf-java-generics

2、https://www.ibm.com/developerworks/cn/java/j-lo-gj/

3、http://peiquan.blog.51cto.com/7518552/1302898

4、https://docs.oracle.com/javase/tutorial/java/generics/

5、Effective Java 2.0

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.