java中的泛型類和泛型方法

1.泛型是什麼?程序員

泛型(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");性能

可是有可能某人已經在該映射中保存了不是 String 的東西,這樣的話,上面的代碼將會拋出 ClassCastException。優化

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

 

2.泛型的好處接口

Java 語言中引入泛型是一個較大的功能加強。不只語言、類型系統和編譯器有了較大的變化,以支持泛型,並且類庫也進行了大翻修,因此許多重要的類,好比集合框架,都已經成爲泛型化的了。這帶來了不少好處:開發

類型安全。 泛型的主要目標是提升 Java 程序的類型安全。經過知道使用泛型定義的變量的類型限制,編譯器能夠在一個高得多的程度上驗證類型假設。沒有泛型,這些假設就只存在於程序員的頭腦中(或者若是幸運的話,還存在於代碼註釋中)。get

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>();
li.put(new Integer(3));
Integer i = li.get(0);


在簡單的程序中使用一次泛型變量不會下降羅嗦程度。可是對於屢次使用泛型變量的大型程序來講,則能夠累積起來下降羅嗦程度。

潛在的性能收益。 泛型爲較大的優化帶來可能。在泛型的初始實現中,編譯器將強制類型轉換(沒有泛型的話,程序員會指定這些強制類型轉換)插入生成的字節碼中。可是更多類型信息可用於編譯器這一事實,爲將來版本的 JVM 的優化帶來可能。

因爲泛型的實現方式,支持泛型(幾乎)不須要 JVM 或類文件更改。全部工做都在編譯器中完成,編譯器生成相似於沒有泛型時所寫的代碼,只是更能確保類型安全而已。

 

3.泛型用法的例子

下面的代碼示例展現了 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>();
m.put("key", "blarg");
String s = m.get("key");

當使用 Map 的泛型化版本時,您再也不須要將 Map.get() 的結果強制類型轉換爲 String,由於編譯器知道 get() 將返回一個 String。

在使用泛型的版本中並無減小鍵盤錄入;實際上,比使用強制類型轉換的版本須要作更多鍵入。使用泛型只是帶來了附加的類型安全。由於編譯器知道關於您將放進 Map 中的鍵和值的類型的更多信息,因此類型檢查從執行時挪到了編譯時,這會提升可靠性並加快開發速度。

 

4.命名類型參數

推薦的命名約定是使用大寫的單個字母名稱做爲類型參數。這與 C++ 約定有所不一樣(參閱 附錄 A:與 C++ 模板的比較),並反映了大多數泛型類將具備少許類型參數的假定。對於常見的泛型模式,推薦的名稱是:

K —— 鍵,好比映射的鍵。 
V —— 值,好比 List 和 Set 的內容,或者 Map 中的值。 
E —— 異常類。 
T —— 泛型。


5.泛型不是協變的

關於泛型的混淆,一個常見的來源就是假設它們像數組同樣是協變的。其實它們不是協變的。List<Object> 不是 List<String> 的父類型。

若是 A 擴展 B,那麼 A 的數組也是 B 的數組,而且徹底能夠在須要 B[] 的地方使用 A[]:

Integer[] intArray = new Integer[10]; 
Number[] numberArray = intArray;

上面的代碼是有效的,由於一個 Integer 是 一個 Number,於是一個 Integer 數組是 一個 Number 數組。可是對於泛型來講則否則。下面的代碼是無效的:

List<Integer> intList = new ArrayList<Integer>();
List<Number> numberList = intList; // invalid

這樣作不少人以爲麻煩,但避免了以下的問題:

Person[] person = new Student[5];

person[0] = new Emploee();

 

6.類型通配符

假設您具備該方法:

void printList(List l) { 
for (Object o : l) 
    System.out.println(o); 
}

上面的代碼在 JDK 5.0 上編譯經過,可是若是試圖用 List<Integer> 調用它,則會獲得警告。出現警告是由於,您將泛型(List<Integer>)傳遞給一個只承諾將它看成 List(所謂的原始類型)的方法,這將破壞使用泛型的類型安全。

若是試圖編寫像下面這樣的方法,那麼將會怎麼樣?

void printList(List<Object> l) { 
for (Object o : l) 
    System.out.println(o); 
}

它仍然不會經過編譯,由於一個 List<Integer> 不是 一個 List<Object>(正如前一屏 泛型不是協變的 中所學的)。這才真正煩人 —— 如今您的泛型版本尚未普通的非泛型版本有用!

解決方案是使用類型通配符:

void printList(List<?> l) { 
for (Object o : l) 
    System.out.println(o); 
}

上面代碼中的問號是一個類型通配符。它讀做「問號」。List<?> 是任何泛型 List 的父類型,因此您徹底能夠將 List<Object>、List<Integer> 或 List<List<List<Flutzpah>>> 傳遞給 printList()。

相關文章
相關標籤/搜索