title: java8教程-泛型(Generics)
date: 2016-06-28 14:04:35
tags:
- java
- 原文鏈接 [https://docs.oracle.com/javase/tutorial/java/generics/index.html)
- 翻譯: Adamin90
- 轉載請注明出處,謝謝!
泛型(已更新)
在任何繁瑣的(nontrivial)軟件項目中,bug是家常便飯。細心的規劃,編程和測試可以幫助減少bug的普遍性(pervasiveness),但是無論如何,無論在哪里,bug總會伺機悄悄溜進(creep)你的代碼,因為很明顯,新的特性會不斷的被引入,并且你的代碼基數會不斷變大和復雜。
幸運的是,一些bug相比其它比較容易檢測。編譯時bug可以在早期被檢測到;你可以利用編譯器的錯誤信息查明是什么問題并且解決,就在那時。然而,運行時bug會更加未預知,他們不會立即展示出來,不知道什么時候發生,可能根本不在程序真正出現問題的點上。
泛型通過更多的在編譯時檢測bug為你的代碼增加了穩定性。
為什么要用泛型
簡言之,泛型能夠使類型(類和接口)在定義類,接口和方法的時候參數化。非常像方法定義時用到的形式參數(formal parameters),類型參數提供了一種你可以通過不同的輸入來復用同一段代碼的方法。不同點是,形式參數輸入的是值,而類型參數輸入的是類型。
使用泛型比非泛型有很多好處:
- 編譯時更強大的類型檢測
Java編譯器對泛型應用了強大的類型檢測,如果代碼違反了類型安全就會報錯。修復編譯時錯誤比修復運行時錯誤更加容易,因為運行時錯誤很難查找到。
- 消除類型轉換(Elimination of casts)
以下代碼片段沒有泛型需要轉型:
List list = new ArrayList();
list.add("hello");
String s = (String) list.get(0);
當我們重新用泛型編寫,代碼就不需要類型轉換了:
List<String> list = new ArrayList<String>();
list.add("hello");
String s = list.get(0); // no cast
- 使開發者實現泛型算法
通過泛型,開發者可以自己實現泛型算法,應用到一系列的不同類型,可以自定義,并且類型安全,易讀。
泛型類型
泛型類型是泛型類或者接口被類型參數化。下面的Box類將被更改演示這個概念。
簡單的 Box 類
列舉一個簡單的非泛型 Box操作任意類型的object。它只需要提供兩個方法:set,添加一個obejct到box,get,獲取這個對象:
public class Box {
private Object object;
public void set(Object object) { this.object = object; }
public Object get() { return object; }
}
因為它的方法接收或返回一個對象,你可以任意傳入,只要傳入的不是原始數據類型。我們沒有在編譯時辨別clas如何使用的。一邊可能替換一個 Integer到box,另一邊獲取的不是Integer類型,而可能傳入一個String類型,結果會導致運行時錯誤。
泛型版本的Box
泛型類的定義形式如下:
class name<T1, T2, ..., Tn> { /* ... */ }
類型參數部分被一對尖括號(<>)劃分,緊跟類名,它指定了類型參數(也叫作類型變量)T1, T2, ....,和Tn.
把原Box類更新為泛型類,你要通過把“public class Box”改變為“public class Box<T>”創建一個類型聲明。這會引入一個類型變量, T,你可以在類中任意地方使用。通過這個改變,Box類就變為:
/**
* Generic version of the Box class.
* @param <T> the type of the value being boxed
*/
public class Box<T> {
// T stands for "Type"
private T t;
public void set(T t) { this.t = t; }
public T get() { return t; }
}
你可以看到,所有Object出現的地方都被替換為T了。一個類型變量可以指定為任意非原始類型的類型:任意的類,任意的接口,任意的數組,甚至其他的類型變量。同樣的技術可以應用到創建泛型接口上。
類型參數命名規則(Naming Conventions)
通過規則,類型參數是單獨的,大寫字母。這個表示鮮明區別了你已知的變量命名規則,一個好的理由是:沒有這個規則,你將很難區分類型變量和原生類或接口名的區別。
最普遍使用的類型參數是:
- E -Element(Java Collections框架大量使用)
- K -Key
- N -Number
- T -Type
- V -Value
- S,U,V 等 -第二,第三,第四個類型
你可以在JAVA SE API 看到這些名字的使用。
調用和實例化一個泛型類型
要在你的代碼引用泛型類 Box,你必須執行 泛型類型調用,把T替換成具體的值,比如Integer:
Box<Integer> integerBox;
你可以認為泛型類型調用跟原生方法調用大致一樣,但是不是傳入一個參數到方法,而是傳入一個類型蠶食--這個情況下的Integer--給Box類本身。
Type Parameter和Type Argument術語(Terminology):
很多開發者交換使用這個兩個術語,但是這兩個術語并不同。敲代碼時,
type argument 創建一個參數化類型,因此,Foo< T>中的T是type parameter,Foo< String> f中的String是一個type argument。
就想其他的變量定義,上面的代碼不會真正創建一個新的 Box對象。它只是聲明,integerBox將持有一個“Box of Integer”的引用,用以讀取Box<Integer>.泛型類型的調用通常稱為參數化類型。
為了實例化這個類,用new 關鍵字,把<Integer>放在類名和括號之間。
Box<Integer> integerBox = new Box<Integer>();
The Diamond
在Java SE 7及以后版本,可以省去類型參數調用泛型類的構造函數,用一個空的類型參數(<>),編譯器可以通過上下文決定,或推測type arguments,這個尖括號非正式得叫作diamond(鉆石?這么奇葩),你可以這樣創建Box< Integer>的一個實例:
Box<Integer> integerBox = new Box<>();
要查看更多關于diamond 符號和類型推斷(inference),請看類型推斷。
多類型參數
正如前面提到的,泛型類可以有多個類型參數。比如泛型 OrderedPair 類,實現了泛型接口 Pair:
public interface Pair<K, V> {
public K getKey();
public V getValue();
}
public class OrderedPair<K, V> implements Pair<K, V> {
private K key;
private V value;
public OrderedPair(K key, V value) {
this.key = key;
this.value = value;
}
public K getKey() { return key; }
public V getValue() { return value; }
}
下面的語句創建了兩個OrderedPair的實例:
Pair<String, Integer> p1 = new OrderedPair<String, Integer>("Even", 8);
Pair<String, String> p2 = new OrderedPair<String, String>("hello", "world");
new OrderedPair<String,Integer>把K實例化為String,V實例化為Integer。因此OrderedPair的參數類型分別(respectively)是String和Integer。因為自動裝箱,傳入String和int到類是有效的。
參數化類型###
你也可以用一個參數化的類型(ie List< String>)替換(substitute)類型參數(K ,V),例如用OrderedPair< K,V>:
OrderedPair<String, Box<Integer>> p = new OrderedPair<>("primes", new Box<Integer>(...));
原類型(Raw Types)
原類型是指泛型類或泛型接口的名字沒有任何參數,比如,給出泛型類Box:
public class Box(T){
public void set(T t){
/* ...... */
}
}
你可以為形參T賦值一個真實的類型參數來創建一個參數化類型的 Box(T):
Box(Ingeter) intBox=new Box<>();
如果真實的類型參數被省略掉了,你就創建了一個原類型的Box<T>:
Box rawBox =new Box();
因此,Box是Box<T>的原類型。然而,非泛型類或非泛型接口沒有原類型。
原類型出現在遺贈的代碼里是因為大量的API類(比如Collections類)在JDK5之前不是泛型類。當使用原類型的時候,你本質上使用的是泛型之前的表現---Box ->Object.為了向后兼容,賦值參數化類型給他的原類型是允許的:
Box<String> stringBox=new Box<>();
Box rawBox=stringBox; //OK
但是如果你賦值一個原類型給一個參數化的類型,你將得到警告:
Box rawBox=new Box(); //rawBox是Box<T>()的原類型
Box<Integer> intBox=rawBox; //warning:unchecked conversion
當你用原類型調用關聯的反省類型的泛型方法時,你也會得到警告:
Box<String> stringBox=new Box<>();
Box rawBox=stringBox;
rawBox.set(8); //waring: unchecked invocation to set(T)
警告顯示原類型繞過泛型類型檢查,延遲捕獲不安全代碼到運行時。因此,你需要避免使用原類型。類型擦除部分會有更多關于Java編譯器如何使用原類型的內容。
Unchecked Error Messages
正如上面提到的,當混合遺贈代碼和泛型代碼時,你可能會碰到跟下面相似的警告:
Note: Example.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
這發生在當使用老的API操作原類型時,例如如下代碼:
public class WarningDemo {
Box<Integer> bi;
bi=createBox();
}
static Box createBox(){
return new Box();
}
'unchecked'指的是編譯器沒有足夠的類型信息來執行所有必要的類型檢查以保證類型安全。