title: java8教程-泛型(Generics)
date: 2016-06-28 14:04:35
tags:
- java
- 原文鏈接 [https://docs.oracle.com/javase/tutorial/java/generics/index.html)
- 翻譯: Adamin90
- 轉(zhuǎn)載請注明出處,謝謝徐鹤!
泛型(已更新)
在任何繁瑣的(nontrivial)軟件項目中脖律,bug是家常便飯废膘。細心的規(guī)劃泳姐,編程和測試可以幫助減少bug的普遍性(pervasiveness),但是無論如何,無論在哪里徒役,bug總會伺機悄悄溜進(creep)你的代碼寞蚌,因為很明顯,新的特性會不斷的被引入麸折,并且你的代碼基數(shù)會不斷變大和復(fù)雜锡凝。
幸運的是,一些bug相比其它比較容易檢測垢啼。編譯時bug可以在早期被檢測到窜锯;你可以利用編譯器的錯誤信息查明是什么問題并且解決,就在那時芭析。然而锚扎,運行時bug會更加未預(yù)知,他們不會立即展示出來,不知道什么時候發(fā)生馁启,可能根本不在程序真正出現(xiàn)問題的點上驾孔。
泛型通過更多的在編譯時檢測bug為你的代碼增加了穩(wěn)定性。
為什么要用泛型
簡言之惯疙,泛型能夠使類型(類和接口)在定義類翠勉,接口和方法的時候參數(shù)化。非常像方法定義時用到的形式參數(shù)(formal parameters),類型參數(shù)提供了一種你可以通過不同的輸入來復(fù)用同一段代碼的方法霉颠。不同點是对碌,形式參數(shù)輸入的是值,而類型參數(shù)輸入的是類型蒿偎。
使用泛型比非泛型有很多好處:
- 編譯時更強大的類型檢測
Java編譯器對泛型應(yīng)用了強大的類型檢測朽们,如果代碼違反了類型安全就會報錯。修復(fù)編譯時錯誤比修復(fù)運行時錯誤更加容易诉位,因為運行時錯誤很難查找到骑脱。
- 消除類型轉(zhuǎn)換(Elimination of casts)
以下代碼片段沒有泛型需要轉(zhuǎn)型:
List list = new ArrayList();
list.add("hello");
String s = (String) list.get(0);
當(dāng)我們重新用泛型編寫,代碼就不需要類型轉(zhuǎn)換了:
List<String> list = new ArrayList<String>();
list.add("hello");
String s = list.get(0); // no cast
- 使開發(fā)者實現(xiàn)泛型算法
通過泛型不从,開發(fā)者可以自己實現(xiàn)泛型算法惜姐,應(yīng)用到一系列的不同類型,可以自定義歹袁,并且類型安全坷衍,易讀枫耳。
泛型類型
泛型類型是泛型類或者接口被類型參數(shù)化。下面的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; }
}
因為它的方法接收或返回一個對象骏全,你可以任意傳入,只要傳入的不是原始數(shù)據(jù)類型尼斧。我們沒有在編譯時辨別clas如何使用的姜贡。一邊可能替換一個 Integer到box,另一邊獲取的不是Integer類型棺棵,而可能傳入一個String類型楼咳,結(jié)果會導(dǎo)致運行時錯誤。
泛型版本的Box
泛型類的定義形式如下:
class name<T1, T2, ..., Tn> { /* ... */ }
類型參數(shù)部分被一對尖括號(<>)劃分律秃,緊跟類名爬橡,它指定了類型參數(shù)(也叫作類型變量)T1, T2棒动, ....,和Tn.
把原Box類更新為泛型類,你要通過把“public class Box”改變?yōu)椤皃ublic class Box<T>”創(chuàng)建一個類型聲明宾添。這會引入一個類型變量, T,你可以在類中任意地方使用船惨。通過這個改變,Box類就變?yōu)椋?/p>
/**
* 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出現(xiàn)的地方都被替換為T了粱锐。一個類型變量可以指定為任意非原始類型的類型:任意的類,任意的接口扛邑,任意的數(shù)組怜浅,甚至其他的類型變量。同樣的技術(shù)可以應(yīng)用到創(chuàng)建泛型接口上。
類型參數(shù)命名規(guī)則(Naming Conventions)
通過規(guī)則恶座,類型參數(shù)是單獨的搀暑,大寫字母。這個表示鮮明區(qū)別了你已知的變量命名規(guī)則跨琳,一個好的理由是:沒有這個規(guī)則自点,你將很難區(qū)分類型變量和原生類或接口名的區(qū)別。
最普遍使用的類型參數(shù)是:
- E -Element(Java Collections框架大量使用)
- K -Key
- N -Number
- T -Type
- V -Value
- S,U,V 等 -第二脉让,第三桂敛,第四個類型
你可以在JAVA SE API 看到這些名字的使用。
調(diào)用和實例化一個泛型類型
要在你的代碼引用泛型類 Box溅潜,你必須執(zhí)行 泛型類型調(diào)用术唬,把T替換成具體的值,比如Integer:
Box<Integer> integerBox;
你可以認(rèn)為泛型類型調(diào)用跟原生方法調(diào)用大致一樣滚澜,但是不是傳入一個參數(shù)到方法粗仓,而是傳入一個類型蠶食--這個情況下的Integer--給Box類本身。
Type Parameter和Type Argument術(shù)語(Terminology):
很多開發(fā)者交換使用這個兩個術(shù)語博秫,但是這兩個術(shù)語并不同潦牛。敲代碼時,
type argument 創(chuàng)建一個參數(shù)化類型挡育,因此巴碗,F(xiàn)oo< T>中的T是type parameter,F(xiàn)oo< String> f中的String是一個type argument即寒。
就想其他的變量定義橡淆,上面的代碼不會真正創(chuàng)建一個新的 Box對象。它只是聲明母赵,integerBox將持有一個“Box of Integer”的引用逸爵,用以讀取Box<Integer>.泛型類型的調(diào)用通常稱為參數(shù)化類型。
為了實例化這個類凹嘲,用new 關(guān)鍵字师倔,把<Integer>放在類名和括號之間。
Box<Integer> integerBox = new Box<Integer>();
The Diamond
在Java SE 7及以后版本周蹭,可以省去類型參數(shù)調(diào)用泛型類的構(gòu)造函數(shù)趋艘,用一個空的類型參數(shù)(<>),編譯器可以通過上下文決定,或推測type arguments凶朗,這個尖括號非正式得叫作diamond(鉆石瓷胧?這么奇葩),你可以這樣創(chuàng)建Box< Integer>的一個實例:
Box<Integer> integerBox = new Box<>();
要查看更多關(guān)于diamond 符號和類型推斷(inference),請看類型推斷棚愤。
多類型參數(shù)
正如前面提到的搓萧,泛型類可以有多個類型參數(shù)。比如泛型 OrderedPair 類,實現(xiàn)了泛型接口 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; }
}
下面的語句創(chuàng)建了兩個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的參數(shù)類型分別(respectively)是String和Integer。因為自動裝箱货矮,傳入String和int到類是有效的羊精。
參數(shù)化類型###
你也可以用一個參數(shù)化的類型(ie List< String>)替換(substitute)類型參數(shù)(K ,V),例如用OrderedPair< K,V>:
OrderedPair<String, Box<Integer>> p = new OrderedPair<>("primes", new Box<Integer>(...));
原類型(Raw Types)
原類型是指泛型類或泛型接口的名字沒有任何參數(shù)囚玫,比如喧锦,給出泛型類Box:
public class Box(T){
public void set(T t){
/* ...... */
}
}
你可以為形參T賦值一個真實的類型參數(shù)來創(chuàng)建一個參數(shù)化類型的 Box(T):
Box(Ingeter) intBox=new Box<>();
如果真實的類型參數(shù)被省略掉了,你就創(chuàng)建了一個原類型的Box<T>:
Box rawBox =new Box();
因此抓督,Box是Box<T>的原類型燃少。然而,非泛型類或非泛型接口沒有原類型铃在。
原類型出現(xiàn)在遺贈的代碼里是因為大量的API類(比如Collections類)在JDK5之前不是泛型類阵具。當(dāng)使用原類型的時候,你本質(zhì)上使用的是泛型之前的表現(xiàn)---Box ->Object.為了向后兼容定铜,賦值參數(shù)化類型給他的原類型是允許的:
Box<String> stringBox=new Box<>();
Box rawBox=stringBox; //OK
但是如果你賦值一個原類型給一個參數(shù)化的類型阳液,你將得到警告:
Box rawBox=new Box(); //rawBox是Box<T>()的原類型
Box<Integer> intBox=rawBox; //warning:unchecked conversion
當(dāng)你用原類型調(diào)用關(guān)聯(lián)的反省類型的泛型方法時,你也會得到警告:
Box<String> stringBox=new Box<>();
Box rawBox=stringBox;
rawBox.set(8); //waring: unchecked invocation to set(T)
警告顯示原類型繞過泛型類型檢查揣炕,延遲捕獲不安全代碼到運行時帘皿。因此,你需要避免使用原類型畸陡。類型擦除部分會有更多關(guān)于Java編譯器如何使用原類型的內(nèi)容鹰溜。
Unchecked Error Messages
正如上面提到的,當(dāng)混合遺贈代碼和泛型代碼時丁恭,你可能會碰到跟下面相似的警告:
Note: Example.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
這發(fā)生在當(dāng)使用老的API操作原類型時曹动,例如如下代碼:
public class WarningDemo {
Box<Integer> bi;
bi=createBox();
}
static Box createBox(){
return new Box();
}
'unchecked'指的是編譯器沒有足夠的類型信息來執(zhí)行所有必要的類型檢查以保證類型安全。