Java泛型总结
0. 概述
泛型是Jdk1.5引入的特性。泛型是Java程序員最常用且最容易被忽視的知識(shí)之一。許多Java程序員只是使用泛型類。但不考慮其工作方式,直到出現(xiàn)問(wèn)題。
?
?
?
1 術(shù)語(yǔ)
用示例進(jìn)行描述。ArrayList<E>類、ArrayList<Integer>類:
- 整個(gè)稱為ArrayList<E> 泛型類型。
- ArrayList<E>中的E稱為 類型變量 或 類型參數(shù)。
- 整個(gè)ArrayList<Integer> 稱為 參數(shù)化的類型。
- ArrayList<Integer>中的Integer稱為 類型參數(shù)的實(shí)例 或 實(shí)際類型參數(shù)。
- ArrayList<Integer>中的<>念著typeof。整個(gè)ArrayList<Integer>稱為"arraylist typof integer"。
- ArrayList稱為原始類型。
?
?
?
2 泛型使用示例
由于經(jīng)常需要使用泛型。所以不再詳解泛型的實(shí)際使用。
此處示例一種遍歷Map的方式。
????????Map<String, Integer> hm = new HashMap<String, Integer>();
????????hm.put("zxx", 19);
????????hm.put("lis", 18);
?
????????Set<Map.Entry<String, Integer>> mes = hm.entrySet();
????????for (Map.Entry<String, Integer> me : mes) {
????????????System.out.println(me.getKey() + ":" + me.getValue());
????????}
?
附:對(duì)在jsp頁(yè)面中也經(jīng)常要對(duì)Set或Map集合進(jìn)行迭代。
<c:forEach items="${map}" var="entry">
????${entry.key}:${entry.value}
</c:forEach>
?
?
?
3 不使用泛型產(chǎn)生的困難
從集合中取出數(shù)據(jù)需要類型轉(zhuǎn)換。從集合中取出的數(shù)據(jù)需要造型,但由于集合中元素的類型沒(méi)有被限制,所以可能造成類型轉(zhuǎn)換錯(cuò)誤。
使用泛型可以不需要再使用強(qiáng)制類型轉(zhuǎn)換。且可以確保類型安全。
由于編譯生成的字節(jié)碼會(huì)去掉泛型的類型信息,只要能跳過(guò)編譯器,就可以往某個(gè)泛型集合中加入其它類型的數(shù)據(jù)。例,用反射得到集合,再調(diào)用其add方法即可。
?
?
?
4 Java泛型原理
Java中的泛型類型(或者泛型)類從表面上似于 C++ 中的模板,但兩者機(jī)制有本質(zhì)的不同。
Java的泛型方法沒(méi)有C++模板函數(shù)功能強(qiáng)大,Java中的如下代碼無(wú)法通過(guò)編譯:
<T> T add(T x,T y) {return (T) (x+y);}
Java中無(wú)論何時(shí)定義一個(gè)泛型類型,都自動(dòng)提供一個(gè)相應(yīng)的原始類型。原始類型的名字就是刪去類型參數(shù)后的泛型類型名。
Java 中的泛型基本上是在編譯器中實(shí)現(xiàn)。編譯器進(jìn)行執(zhí)行類型檢查和類型推斷,然后生成普通的非泛型的字節(jié)碼。編譯器使用泛型類型信息保證類型安全,然后在生成字節(jié)碼之前將其清除。這種實(shí)現(xiàn)技術(shù)稱為擦除(erasure)。
?所以泛型實(shí)際是提供給Javac編譯器使用的。限定輸入類型,讓編譯器擋住源程序中的非法輸入,編譯器編譯帶類型說(shuō)明的集合時(shí)會(huì)去除掉"類型"信息。程序運(yùn)行期間,沒(méi)有任何泛型泛型的痕跡。使程序運(yùn)行效率不受影響,對(duì)于參數(shù)化的泛型類型,getClass()方法的返回值和原始類型完全一樣。
例:通過(guò)以下代碼,可以看出運(yùn)行期泛型類型是被擦除的。
import java.util.ArrayList;
import java.util.List;
?
public class TestMain {
????public static void main(String[] args) {
?
????????List l = new ArrayList();
????????System.out.println(l.getClass());
????????
????????List<String> ls = new ArrayList<String>();
????????System.out.println(ls.getClass());
????????
????????List<Object> lo = new ArrayList<Object>();
????????System.out.println(lo.getClass());
????????
????????System.out.println(l.getClass().equals(ls.getClass())); //比較ArrayList與ArrayList<String>
????????System.out.println(l.getClass().equals(lo.getClass())); //比較ArrayList與List<Object>
????????System.out.println(lo.getClass().equals(ls.getClass())); //比較ArrayList<String>與List<Object>
????????
????}
}
輸出:
class java.util.ArrayList
class java.util.ArrayList
class java.util.ArrayList
true
true
true
?
?
例:使用反射機(jī)制繞過(guò)泛型的編譯器檢查。由于程序在運(yùn)行期間,不再帶有泛型約束,所以程序正確打印結(jié)果。
public class TestMain {
????public static void main(String[] args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException {
????????List<Integer> ls = new ArrayList<Integer>();
????????//ls.add("abc"); //編譯器報(bào)錯(cuò)
????????ls.getClass().getMethod("add", Object.class).invoke(ls,"abc");
????????System.out.println(ls);
????}
}
輸出:
[abc]
?
?
?
5 正確使用泛型
5.1 參數(shù)化類型與原始類型的兼容性
參數(shù)化類型可以引用一個(gè)原始類型的對(duì)象。編譯器提示警告。
例,Collection<String> c = new ArrayList();
?
原始類型可以引用一個(gè)參數(shù)化類型的對(duì)象。編譯器提示警告。
例,Collection c = new ArrayList<String>();
?
參數(shù)化類型不考慮類型參數(shù)的繼承關(guān)系。
例:ArrayList<String> a = new ArrayList<Object>(); 編譯器提示錯(cuò)誤。
例:ArrayList<Object> a = new ArrayList<String>(); 編譯器提示錯(cuò)誤。
?
編譯器不允許創(chuàng)建泛型變量的數(shù)組。即在創(chuàng)建數(shù)組實(shí)例時(shí),數(shù)組的元素不能使用參數(shù)化的類型
例:ArrayList<Integer>[] a1 = new ArrayList<Integer>[10];
或 ArrayList<Integer> a2[] = new ArrayList<Integer>[10]; 編譯器提示錯(cuò)誤。
?
強(qiáng)調(diào),泛型只是在編譯期間編譯器做類型檢查的機(jī)制。
例:以下示例可以通過(guò)編譯。編譯器只是提示警告。
此處的a對(duì)象,本質(zhì)上變?yōu)锳rrayList<Object> 。
或
此處的a對(duì)象,本質(zhì)上變?yōu)锳rrayList<String> 。
?
?
5.2 Java泛型的約束和局限性
(1)不能使用基本類型實(shí)例化類型參數(shù)
不存在Pair<double>,只有Pair<Double>。由于類型擦除,Pair類含有Object類型的域,而Object不能存儲(chǔ)double。
但該缺陷,可以使用包裝類型解決。
重要結(jié)論:使用泛型或定義泛型類、泛型方法時(shí),泛型必須是引用類型,不能是基本類型。
?
(2)不能拋出也不能捕獲泛型類實(shí)例
泛型類不能繼承Throwable。
不能在catch子句中使用泛型類型變量。
?
(3)參數(shù)化類型的數(shù)組不合法
?
(4)不能實(shí)例化類型變量
若需要在方法中對(duì)泛型類型實(shí)例化,則需要傳入Class<T>。
public static <T> void makePair(Class<T> cl) throws InstantiationException, IllegalAccessException{
T t = cl.newInstance();
}
?
(5)泛型類的靜態(tài)上下文中類型變量無(wú)效
不能在靜態(tài)域或方法中應(yīng)用類型變量。
?
(6)擦除后的沖突
以下代碼無(wú)法通過(guò)編譯
equals(T obj)與equals(Object obj)沖突。擦除類型后,equals(T obj)就是equals(Object obj)
?
?
?
6 定義泛型類
泛型類是具有一個(gè)或多個(gè)類型變量的類。可以把泛型類看作是普通類的"工廠"。
自定義泛型使用尖括號(hào)<>聲明泛型參數(shù)。泛型類基本格式:
class CLS<T, U> {
????void method1(T param) {
????}
?
????T method2(T param) {
????????// return
????}
?
????U method3(T param1, U param2) {
????????// return ;
????}
}
?
一般Java中,使用變量E表示集合元素類型。K表示關(guān)鍵字,V表示值。T(或者U、S)表示"任意類型"。
當(dāng)一個(gè)變量被聲明為泛型時(shí),只能被實(shí)例變量、方法和內(nèi)部類調(diào)用,而不能被靜態(tài)變量和靜態(tài)方法調(diào)用。因?yàn)殪o態(tài)成員是被所有參數(shù)化的類所共享的,所以靜態(tài)成員不應(yīng)該有類級(jí)別的類型參數(shù)。若使用需要是靜態(tài)泛型方法,則重新聲明泛型參數(shù)。
?
例:定義泛型GenericDao 。
public class GenericDao<T> {
????public void save(T obj) {
????}
????public T getById(int id) {
????????return null;
????}
????/**
???? * 靜態(tài)方法。重新聲明T,此處的T與GenericDao<T>的T沒(méi)有關(guān)系。
???? */
????public static <T> void update(T obj){
????}
}
?
GenericDao引入類型變量T,用尖括號(hào)<> 括起來(lái),并放在類名后面。泛型類可以有多個(gè)類型變量,如:GenericDao<T,U>
?
在引用類名時(shí)指定的泛型類的泛型參數(shù)類型。
例,如下兩種方式都可以:
GenericDao<String> dao = null;
????new GenericDao<String>();
?
?
7 自定義泛型方法
用于放置泛型的類型參數(shù)的尖括號(hào)應(yīng)出現(xiàn)在方法的其他所有修飾符之后和在方法的返回類型之前,也就是緊鄰返回值之前。
按照慣例,類型參數(shù)通常用單個(gè)大寫字母表示。
普通方法、構(gòu)造方法和靜態(tài)方法中都可以使用泛型。
?
例:
原方法為:
????static int method(int x,int y){
????????return 0;
????}
????
泛型方法為:
????static <T> T method(T x,T y){
????????return null;
????}
<T>用于聲明一個(gè)泛型參數(shù)。
?
7.1 使用泛型化方法
當(dāng)調(diào)用一個(gè)泛型方法時(shí),在方法名前的尖括號(hào)中放入具體的類型。
例:使用調(diào)用TestMain類中的泛型化的靜態(tài)方法method。
String s = TestMain.<String>method("a","b");
必須有類名在最前面引出method方法,否則編譯器報(bào)錯(cuò)。
?
由于編譯器存在類型推斷機(jī)制,會(huì)自動(dòng)推斷出調(diào)用方法的泛型類型。所以使用泛型方法可以不需要指明泛型類型。
例:調(diào)用泛型化的method()方法。
?
例:交換數(shù)組中的兩個(gè)元素的位置的泛型方法語(yǔ)法定義如下:
????static <T> void swap(T[] a,int i,int j){
????????T tmp = a[i];
????????a[j]=a[i];
????????a[i]=tmp;
????}
使用swap方法:
?
說(shuō)明:
只有引用類型才能作為泛型方法的實(shí)際參數(shù)。swap(new int[]{1,2,3,4},1,3);編譯錯(cuò)誤。
?
在泛型中可以同時(shí)有多個(gè)類型參數(shù),在定義它們的尖括號(hào)中用逗號(hào)分。
例:
public static <K, V> V getValue(Map<K,V> map,K key) {
????????return map.get(key);
}
?
?
7.2 泛型方法例題
例:編寫一個(gè)泛型方法,自動(dòng)將Object類型的對(duì)象轉(zhuǎn)換成其他類型。
解:
public static <T> T autoConvert(Object obj){
????????return (T)obj;
????}
使用autoConvert方法。
????Object obj = "abc";
????String abc1 = (String)obj;
????String abc2 = autoConvert(obj);
?
例:定義一個(gè)方法,可以將任意類型的數(shù)組中的所有元素填充為相應(yīng)類型的某個(gè)對(duì)象。
????static <T> void fillArray(T[] a,T obj){
????????for(int i=0;i<a.length;i++){
????????????a[i]=obj;
????????}
????}
?
例:采用自定泛型方法的方式打印出任意參數(shù)化類型的集合中的所有內(nèi)容。
????static <T> void printCollection(Collection<T> collection){
????????for (Object obj : collection) {
????????????System.out.println(obj);
????????}
????}
說(shuō)明:該例使用之前的通配符方案要比范型方法更有效。
當(dāng)一個(gè)類型變量用來(lái)表達(dá)兩個(gè)參數(shù)之間或者參數(shù)和返回值之間的關(guān)系時(shí),即同一個(gè)類型變量在方法簽名的兩處被使用,或者類型變量在方法體代碼中也被使用而不是僅在簽名的時(shí)候使用,才需要使用范型方法。
?
?
例:定義一個(gè)方法,把任意參數(shù)類型的集合中的數(shù)據(jù)安全地復(fù)制到相應(yīng)類型的數(shù)組中。
static <T> void copy1(Collection<T> dest,T[] src){
????????// ...略
????}
????
????static <T> void copy2(T[] dest,T[] src){
????????// ...略
????}
?
使用copy1方法和copy2方法:
//copy1泛型方法的T的類型Object。因?yàn)锳rrayList與String父類的交集為Object。
copy1(new ArrayList(),new String[10]);
//copy2泛型方法的T的類型Object。因?yàn)镈ate與String父類的交集為Object。
copy2(new Date[10],new String[10]);
?
//new ArrayList<Date>()確定了T為Data,而new String[10]又指明T為String。
//由于類型推斷的傳播性,導(dǎo)致編譯器報(bào)錯(cuò)。
copy1(new ArrayList<Date>(),new String[10]);
?
?
8 泛型類型變量的限定
使用< T extends BoundingType >限定泛型類型。T應(yīng)該是綁定類型的子類型。T和綁定類型可以是類或接口。
例:<T extends Comparable & Serializable>
?
例:Class.getAnnotation()方法的定義。
public <A extends Annotation> A getAnnotation(Class<A> annotationClass)
<A extends Annotation> 表示聲明一個(gè)泛型參數(shù),該參數(shù)類型必須是Annotation類或其的子類。
?
例:通用泛型Dao。
public interface GenericDao<T extends Serializable,ID extends Serializable> {
}
?
public abstract class GenericDaoImpl<T extends Serializable ,ID extends Serializable> implements GenericDao<T, ID> {
}
?
一個(gè)泛型變量或通配符可以有多個(gè)限定。限定類型使用"&"分割。若用一個(gè)類做限定,它必須是限定列表中的第一個(gè)。
例:<V extends Serializable & Cloneable> void method(){}
<V extends Serializable & cloneable> 表示聲明一個(gè)泛型參數(shù),參數(shù)類型必須繼承Serializable 且必須繼承Cloneable
?
可以用類型變量表示異常,稱為參數(shù)化的異常,可以用于方法的throws列表中,但是不能用于catch子句中。
例:
private static <T extends Exception> void sayHello() throws T {
try {
?
????????} catch (Exception e) {
????????????throw (T) e;
????}
}
?
?
?
9 泛型通配符
使用"?"通配符可以引用其他各種參數(shù)化的類型,"?"通配符定義的變量主要用作引用,可以調(diào)用與參數(shù)化無(wú)關(guān)的方法,不能調(diào)用與參數(shù)化有關(guān)的方法。
例:Collection<?>可以適配Collection<Object>、Collection<Integer>或Collection<String>等。
?
例:定義一個(gè)方法,該方法用于打印出任意參數(shù)化類型的集合中的所有數(shù)據(jù),該方法如何定義呢?
錯(cuò)誤方式:
????public static void printCollection(Collection<Object> cols) {
????????for (Object obj : cols) {
????????????System.out.println(obj);
????????}
????}
說(shuō)明:該方法只能接收Collection<Object>類型的參數(shù)。對(duì)Collection<Integer>或Collection<String>無(wú)法接收。
正確方式:
不使用泛型,編譯器提示警告。
????public static void printCollection(Collection cols) {
????????for (Object obj : cols) {
????????????System.out.println(obj);
????????}
????}
利用泛型通配符,使用泛型。
????public static void printCollection(Collection<?> cols) {
????????for (Object obj : cols) {
????????????System.out.println(obj);
????????}
????}
說(shuō)明:該方法可以接收Collection<Integer>、Collection<Object>或Collection<String>等。
?
參數(shù)通配符使用的聲明的類型,是不確定類型的。在使用的時(shí)候,可能會(huì)造成一些錯(cuò)誤。
例:定義兩個(gè)方法method1與method2。
?
?
9.1 限定通配符的上邊界
<? extends Number>用于匹配Number及Number的子類。
正確:Vector<? extends Number> x = new Vector<Integer>(); Integer繼承了Number
錯(cuò)誤:Vector<? extends Number> x = new Vector<String>();
?
9.2限定通配符的下邊界
<? super Integer>用于匹配Integer及Integer的父類。
正確:Vector<? super Integer> x = new Vector<Number>();
錯(cuò)誤:Vector<? super Integer> x = new Vector<Byte>();
?
說(shuō)明:
限定通配符總是包括自己。
?只能用作引用,不能用它去給其他變量賦值
????Vector<? extends Number> y = new Vector<Integer>();
????Vector<Number> x = y; 錯(cuò)誤,原理與Vector<Object > x11 = new Vector<String>();相似,只能通過(guò)強(qiáng)制類型轉(zhuǎn)換方式來(lái)賦值。
?
?
10 泛型類型推斷
例,對(duì)于上面定義的static <T> T method(T x,T y)方法。注意其返回類型。
Integer i = method(1,2); //1與2都是Integer,所以返回Integer
Double d = method(1.2,2.3); //1.2,2.3默認(rèn)都是Double,所以返回Double
Number n = method(1.2,2); //1.2是Double,2是Integer,兩者均能匹配Number。返回Number。
Object o = method(1.2,"2"); //1.2是Double,"2"是String,這者均能匹配Object。返回Object。
method的返回類型是參數(shù)x與參數(shù)y允許允許匹配類型的交集。
?
編譯器判斷范型方法的實(shí)際類型參數(shù)的過(guò)程稱為類型推斷。類型推斷是相對(duì)于知覺(jué)推斷的,其實(shí)現(xiàn)方法是一種非常復(fù)雜的過(guò)程。
根據(jù)調(diào)用泛型方法時(shí)實(shí)際傳遞的參數(shù)類型或返回值的類型來(lái)推斷,具體規(guī)則如下:
- 當(dāng)某個(gè)類型變量只在整個(gè)參數(shù)列表中的所有參數(shù)和返回值中的一處被應(yīng)用了,那么根據(jù)調(diào)用方法時(shí)該處的實(shí)際應(yīng)用類型來(lái)確定,這很容易憑著感覺(jué)推斷出來(lái),即直接根據(jù)調(diào)用方法時(shí)傳遞的參數(shù)類型或返回值來(lái)決定泛型參數(shù)的類型。
例: swap(new String[3],3,4) ---> static <E> void swap(E[] a, int i, int j) - 當(dāng)某個(gè)類型變量在整個(gè)參數(shù)列表中的所有參數(shù)和返回值中的多處被應(yīng)用了,如果調(diào)用方法時(shí)這多處的實(shí)際應(yīng)用類型都對(duì)應(yīng)同一種類型來(lái)確定,這很容易憑著感覺(jué)推斷出來(lái)。
例:add(3,5) ---> static <T> T add(T a, T b) - 當(dāng)某個(gè)類型變量在整個(gè)參數(shù)列表中的所有參數(shù)和返回值中的多處被應(yīng)用了,如果調(diào)用方法時(shí)這多處的實(shí)際應(yīng)用類型對(duì)應(yīng)到了不同的類型,且沒(méi)有使用返回值,這時(shí)候取多個(gè)參數(shù)中的最大交集類型
例:下面語(yǔ)句實(shí)際對(duì)應(yīng)的類型就是Number了,編譯沒(méi)問(wèn)題,只是運(yùn)行時(shí)出問(wèn)題。
fill(new Integer[3],3.5f) ---> static <T> void fill(T[] a, T v) - 當(dāng)某個(gè)類型變量在整個(gè)參數(shù)列表中的所有參數(shù)和返回值中的多處被應(yīng)用了,如果調(diào)用方法時(shí)這多處的實(shí)際應(yīng)用類型對(duì)應(yīng)到了不同的類型, 并且使用返回值,這時(shí)候優(yōu)先考慮返回值的類型。
例如,下面語(yǔ)句實(shí)際對(duì)應(yīng)的類型就是Integer了,編譯將報(bào)告錯(cuò)誤,將變量x的類型改為float,對(duì)比eclipse報(bào)告的錯(cuò)誤提示,接著再將變量x類型改為Number,則沒(méi)有了錯(cuò)誤。
int x =(3,3.5f) ---> static <T> T add(T a, T b) - 參數(shù)類型的類型推斷具有傳遞性,下面第一種情況推斷實(shí)際參數(shù)類型為Object,編譯沒(méi)有問(wèn)題,而第二種情況則根據(jù)參數(shù)化的Vector類實(shí)例將類型變量直接確定為String類型,編譯將出現(xiàn)問(wèn)題。
例:copy(new Integer[5],new String[5]) ---> static <T> void copy(T[] a,T[] b);
copy(new Vector<String>(), new Integer[5]) ---> static <T> void copy(Collection<T> a , T[] b);
?
?
?
11 通過(guò)反射獲取泛型類型
如List<Date> list= new ArrayList<Date>(); 通過(guò)反射獲取list的類型。這是很多框架內(nèi)部實(shí)現(xiàn)的常見(jiàn)需求。
雖然Java編譯器會(huì)把泛型類型擦除,但擦除的類仍然保留一些泛型祖先的微弱記憶。
?
方法一:反射相關(guān)的Method對(duì)象,可以獲取其代表方法的參數(shù)所帶的泛型信息。
示例代碼:
public class GenericalReflection {
????private List<Date> dates = new ArrayList<Date>();
?
????public void setDates(List<Date> dates) {
????????this.dates = dates;
????}
?
????public static void main(String[] args) throws SecurityException, NoSuchMethodException {
????????//獲取setDates的Method對(duì)象
????????Method methodApply = GenericalReflection.class.getDeclaredMethod("setDates", List.class);
????????Type[] gTypes= methodApply.getGenericParameterTypes();
????????ParameterizedType pType = (ParameterizedType) (gTypes)[0];
????????Type[] actualTypeArguments = pType.getActualTypeArguments();
????????//輸出Method對(duì)象泛型擦除后的類型。
????????System.out.println(((Class)pType.getRawType()).getName());
????????//輸出Method對(duì)象泛型信息。
System.out.println(((Class)actualTypeArguments[0]).getName());
????}
}
?
方法二:通過(guò)使用class實(shí)例的getGenericSuperclass()方法獲取泛型信息。
例:泛型DAO實(shí)現(xiàn)類中獲取泛型類型。
public interface BaseDao<T> {
}
?
@SuppressWarnings("unchecked")
public abstract class BaseDaoImpl<T> implements BaseDao<T> {
????protected Class<T> clazz;
?
????public BaseDaoImpl() {
????????Type type = this.getClass().getGenericSuperclass();
????????ParameterizedType pt = (ParameterizedType) type;
????????this.clazz = (Class) pt.getActualTypeArguments()[0];
????????System.out.println("clazz = " + this.clazz);
????}
}
?
?
轉(zhuǎn)載于:https://www.cnblogs.com/shijiaqi1066/p/3441445.html
總結(jié)
- 上一篇: 关于loader加载的东西必须是继承sp
- 下一篇: web.xml 配置 加载顺序