天天看點

java泛型

什麼是泛型?

泛型(Generic type 或者 generics)是對 Java 語言的類型系統的一種擴充,以支援建立可以按類型進行參數化的類。可以把類型參數看作是使用參數化類型時指定的類型的一個占位符,就像方法的形式參數是運作時傳遞的值的占位符一樣。

可以在集合架構(Collection framework)中看到泛型的動機。例如,Map 類允許您向一個 Map 添加任意類的對象,即使最常見的情況是在給定映射(map)中儲存某個特定類型(比如 String)的對象。

因為 Map.get() 被定義為傳回 Object,是以一般必須将 Map.get() 的結果強制類型轉換為期望的類型,如下面的代碼所示:

Map m = new HashMap();

m.put("key", "blarg");

String s = (String) m.get("key");

要讓程式通過編譯,必須将 get() 的結果強制類型轉換為 String,并且希望結果真的是一個 String。但是有可能某人已經在該映射中儲存了不是 String 的東西,這樣的話,上面的代碼将會抛出 ClassCastException。

理想情況下,您可能會得出這樣一個觀點,即 m 是一個 Map,它将 String 鍵映射到 String 值。這可以讓您消除代碼中的強制類型轉換,同時獲得一個附加的類型檢查層,該檢查層可以防止有人将錯誤類型的鍵或值儲存在集合中。這就是泛型所做的工作。

泛型的好處

Java 語言中引入泛型是一個較大的功能增強。不僅語言、類型系統和編譯器有了較大的變化,以支援泛型,而且類庫也進行了大翻修,是以許多重要的類,比如集合架構,都已經成為泛型化的了。這帶來了很多好處:

類型安全。 泛型的主要目标是提高 Java 程式的類型安全。通過知道使用泛型定義的變量的類型限制,編譯器可以在一個高得多的程度上驗證類型假設。沒有泛型,這些假設就隻存在于程式員的頭腦中(或者如果幸運的話,還存在于代碼注釋中)。

Java 程式中的一種流行技術是定義這樣的集合,即它的元素或鍵是公共類型的,比如“String 清單”或者“String 到 String 的映射”。通過在變量聲明中捕獲這一附加的類型資訊,泛型允許編譯器實施這些附加的類型限制。類型錯誤現在就可以在編譯時被捕獲了,而不是在運作時當作 ClassCastException 展示出來。将類型檢查從運作時挪到編譯時有助于您更容易找到錯誤,并可提高程式的可靠性。

消除強制類型轉換。 泛型的一個附帶好處是,消除源代碼中的許多強制類型轉換。這使得代碼更加可讀,并且減少了出錯機會。

盡管減少強制類型轉換可以降低使用泛型類的代碼的羅嗦程度,但是聲明泛型變量會帶來相應的羅嗦。比較下面兩個代碼例子。

該代碼不使用泛型:

List li = new ArrayList();

li.put(new Integer(3));

Integer i = (Integer) li.get(0);

該代碼使用泛型:

List<Integer> li = new ArrayList<Integer>();

Integer i = li.get(0);

在簡單的程式中使用一次泛型變量不會降低羅嗦程度。但是對于多次使用泛型變量的大型程式來說,則可以累積起來降低羅嗦程度。

潛在的性能收益。 泛型為較大的優化帶來可能。在泛型的初始實作中,編譯器将強制類型轉換(沒有泛型的話,程式員會指定這些強制類型轉換)插入生成的位元組碼中。但是更多類型資訊可用于編譯器這一事實,為未來版本的 JVM 的優化帶來可能。

由于泛型的實作方式,支援泛型(幾乎)不需要 JVM 或類檔案更改。所有工作都在編譯器中完成,編譯器生成類似于沒有泛型(和強制類型轉換)時所寫的代碼,隻是更能確定類型安全而已。

泛型用法的例子

泛型的許多最佳例子都來自集合架構,因為泛型讓您在儲存在集合中的元素上指定類型限制。考慮這個使用 Map 類的例子,其中涉及一定程度的優化,即 Map.get() 傳回的結果将确實是一個 String:

如果有人已經在映射中放置了不是 String 的其他東西,上面的代碼将會抛出 ClassCastException。泛型允許您表達這樣的類型限制,即 m 是一個将 String 鍵映射到 String 值的 Map。這可以消除代碼中的強制類型轉換,同時獲得一個附加的類型檢查層,這個檢查層可以防止有人将錯誤類型的鍵或值儲存在集合中。

下面的代碼示例展示了 JDK 5.0 中集合架構中的 Map 接口的定義的一部分:

public interface Map<K, V> {

public void put(K key, V value);

public V get(K key);

}

注意該接口的兩個附加物:

類型參數 K 和 V 在類級别的規格說明,表示在聲明一個 Map 類型的變量時指定的類型的占位符。

在 get()、put() 和其他方法的方法簽名中使用的 K 和 V。

為了赢得使用泛型的好處,必須在定義或執行個體化 Map 類型的變量時為 K 和 V 提供具體的值。以一種相對直覺的方式做這件事:

Map<String, String> m = new HashMap<String, String>();

String s = m.get("key");

當使用 Map 的泛型化版本時,您不再需要将 Map.get() 的結果強制類型轉換為 String,因為編譯器知道 get() 将傳回一個 String。

在使用泛型的版本中并沒有減少鍵盤錄入;實際上,比使用強制類型轉換的版本需要做更多鍵入。使用泛型隻是帶來了附加的類型安全。因為編譯器知道關于您将放進 Map 中的鍵和值的類型的更多資訊,是以類型檢查從執行時挪到了編譯時,這會提高可靠性并加快開發速度。

向後相容

在 Java 語言中引入泛型的一個重要目标就是維護向後相容。盡管 JDK 5.0 的标準類庫中的許多類,比如集合架構,都已經泛型化了,但是使用集合類(比如 HashMap 和 ArrayList)的現有代碼将繼續不加修改地在 JDK 5.0 中工作。當然,沒有利用泛型的現有代碼将不會赢得泛型的類型安全好處。