引言:泛型一直是困擾自己的一個難題,但是泛型有時一個面試時老生常談的問題;今天作者就通過查閱相關(guān)資料簡單談談自己對泛型的理解;
一:什么是泛型:
- 泛型就是參數(shù)化類型,就是所操作的數(shù)據(jù)類型被指定為一個參數(shù),這種類型可以在類接口和方法中創(chuàng)建,分別稱之為泛型類,泛型接口,泛型方法
二:為什么要使用泛型:
- 使用泛型可以讓我們編寫的代碼可以被很多類型的對象重用;
例如我們不希望為String和file對象分別設計不同的類;實際上我們也并不需要這么做;
就拿ArrayList類舉例:可以聚集任何類型的對象;Javase5.0之前是使用繼承實現(xiàn)的;ArrayList類只維護一個Object引用的數(shù)組;
Public class ArrayList{
Public Object get(int i){
}
Public void add(Object o){
}
Public Object[] elementData;
}
問題1:獲取值必須進行強制類型轉(zhuǎn)換;
問題2:沒有錯誤檢查可以向數(shù)組列表中添加任何參數(shù);
針對以上兩種情況泛型提出了更好的解決方案;類型參數(shù);(type parameters)使我們設計的程序具有更好的可讀性和安全性
三:泛型都有哪些?
-
1:泛型類:
就是一個或者多個具有類型變量的類;如下:
public class Pair<T>{
private T first;
private T second;
public Pair(){
first = null;
second = null;
}
public void setFirst(T first){
this.first = first;
}
public void setSecond(T second){
this.second = second
}
public T getFirst(){
return this.first;
}
public T getSecond(){
return Second;
}
}
Pair類引入了一個類型變量T,將其放在"<>"里面;并且將其放在類名的后面,泛型類也可以有多個類型變量;
可以將泛型類看做是普通類的工廠;
-
2:泛型方法:
帶有類型參數(shù)的簡單方法;
泛型方法可以定義在普通類里面也可以定義在泛型類里面;
所謂的泛型方法要么就是返回值是一個泛型,要么就是參數(shù)是泛型;class ArrayAlg{ public static <T> T getMiddle(T[] a){ return a[a.length/2] } }
-
3:泛型接口
就是一個或者多個具有類型變量的接口
pulbic interface Collection<E> extends Iterable<E> {
Iterator<E> iterator();
}
四:泛型使用方法
-
1:泛型代碼和虛擬機
注意虛擬機中沒有泛型類型的對象,所有的對象都屬于普通類;
無論什么時候定義泛型,java虛擬機都會自動提供一個相應的原始類型(raw type);原始類型的名字就是刪除泛型參數(shù)之后的泛型類型名;擦除類型變量,并替換為限定類型,如果沒有限定類型的變量就替換成Object;
如上面的Pair:替換之后的代碼為:
public class Pair{
private Object first;
private Object second;
public Pair(){
first = null;
second = null;
}
public void setFirst(Object first){
this.first = first;
}
public void setSecond(Object second){
this.second = second
}
public Object getFirst(){
return this.first;
}
public Object getSecond(){
return Second;
}
}
如果泛型類使用了限定類型如下:
public class Interval<T extends Comparable & Serializabler>{
public Interval(T first,T second){
if(first.comparaTo(second)<=0){
lower - first;upper = second;
}
....
private T lower;
private T upper;
}
}
//轉(zhuǎn)換成原始類型之后
public class Interval extends erializabler{
public Interval(Comparable first,Comparable second){
if(first.comparaTo(second)<=0){
lower - first;upper = second;
}
....
private Comparable lower;
private Comparable upper;
}
}
<1>:翻譯泛型表達式
當程序調(diào)用泛型方法時,如果擦除返回類型,編譯器就會插入強制類型轉(zhuǎn)換;eg:
Pair<Employee> buddies = ...;
Employee buudy = buudies.getFirst();
//這里擦書getFirst的返回類型后將返回Object類型。編譯器自動插入Employee的強制類型轉(zhuǎn)換。也就是說編譯器把這個方法翻譯為兩條虛擬機指令:
A:對原始方法Pair.getFirst的調(diào)用
B:將返回的Object類型強制轉(zhuǎn)換為Employee類型
<2>:翻譯泛型方法
public static <T extends Comparable> T min(T[] a)是一個完整的方法族,擦除類型之后只剩下一個方法了:
public static Comparable min(Comparable[] a)
此時僅僅留下限定類型Comparable
eg: class DateInterval extends Pair<Date>{
public void setSecond(Date second){
if(second.compareTo(getFirst())>=0){
super.setSecond(second);
}
}
}
//類型擦出之后:
class DateInterval extends Pair{
public void setSecond(Date second){
if(second.compareTo(getFirst())>=0){
super.setSecond(second);
}
}
}
此時存在另一個從Pair中繼承來的setSecond方法,即
public void setSecond(Object second)
這個方法顯然和setSecond(Date date)不是同一個方法;
考慮下面的代碼:
DateInterval interval = new DateInterval(...);
Pair<Date> pair = interval;
pair.setSecond(aDate);
這里我們希望setSecond的調(diào)用具有多態(tài)性;并調(diào)用最合適的那個方法;希望pair調(diào)用DateInterval.setSecond方法;但此時類型擦書和多態(tài)已經(jīng)發(fā)生了沖突:想要解決這個問題我們需要建立一個橋方法;
public void setSecond(Object second){
setSecond((Date) second)
}
總之:記住有關(guān)java泛型轉(zhuǎn)換的事實:
A:虛擬機中沒有泛型,只有普通方法和普通類;
B:所有的類型參數(shù)都用他們的限定類型替換
C:橋方法被合成用來保持多態(tài);
D:為保持類型安全性,必要時插入強制類型轉(zhuǎn)換。
2:約束和局限性:
使用java中的泛型時需要考慮一些限制,大多是限制都是由于類型擦除引起的;
<1>:不能用基本類型實例化類型參數(shù)
沒有Pair<double>只有Pair<Double>
<2>:運行時類查詢只適用于原始類型
虛擬機中的對象總有一個特定的非泛型類型。因此所有的類型查詢只產(chǎn)生原始類型。例如:
if(a instanceof Pair<String>)//ERROR
<3>:不能創(chuàng)建參數(shù)化類型的數(shù)組:
Pair<String>[] table = new Pair<String>[10]//ERROR;
但是聲明類型Pair<String>[]的變量仍是合法的;
<4>:Varargs警告:
由于java布置池泛型類型的數(shù)組;當我們向一個參數(shù)個數(shù)可變的方法 傳遞一個泛型類型的實例:
eg:
public static <T> void addAll(Collection<T> coll,T...ts){
for(T t:ts)coll.add(t);
}
實際上ts是一個數(shù)組,包含提供的所有實參。
Collection<Pair<String>> table = ...;
Pair<String> pair1 = ....;
Pair<String> pair2 = ....;
Pair<String> pair3 = ....;
addAll(table,pair1,pair2);
為了調(diào)用這個方法,Java虛擬機必須建立一個Pair<String>數(shù)組;違反前面的規(guī)定;但是這種情況并不會報告錯誤僅僅會報告一個警告可以通過兩種方式去消除它:
A:為addAll方法添加標注@SuppressWarning("unchecked").
B:@SafeVarargs直接標志addAll方法
@SafeVarags
public static <T> void addAll(Collection<T> coll,T...ts)
#####<5>:不能實例化類型參數(shù)
new T(...),new T[]或者T.class都是不允許的;
但是可以通過反射加上一定的API便可實現(xiàn)泛型的實例化;
```java
public static <T> Pair<T> makePair(Class<T> cl){
try{
return new Pair<>(cl.newIntance(),cl.newInstance())
}catch(Exception e){
return null;
}
}
Pair<String> p = Pair.makePair(String.class)
類型擦除會讓這個方法永遠構(gòu)造Object[2]數(shù)組;
如果數(shù)組僅僅作為一個類的私有實例域,就可以將這個數(shù)組聲明為Object[],并在獲取元素時進行類型轉(zhuǎn)換。例如ArrayList就是這樣實現(xiàn)的;
Public class ArrayList<E>{
Private Object[] elements;
@SuppressWarning(‘unchecked’)
public E get(int n){
return (E) elements[n]}
}
Public void set(int n,E e){elements[n] = e};
Public class ArrayList<E>{
Private E[] elements;
Public ArrayList(){
Elements = (E[])new Object[10];//假像:類型擦除會使其無法差距
}
}
編譯時不會報錯,但是當我們的程序執(zhí)行時,當我們把Object[]引用賦值給T[]時就會報錯;將會發(fā)生ClassCastException異常;
這種情況下我們可以利用反射:
Public static <T extends Comparable> T[] minmax(T…a){
T[] mm = (T[])Array.newInstance(a.getClass().getComponentType(),2)
}
<6>泛型類的靜態(tài)上下文中類型變量無效
不能在靜態(tài)域或方法中引用類型變量(即泛型);(即被static修飾的域或者方法)
<7>:不能拋出或者捕獲泛型類的實例
既不能拋出也不能捕獲泛型類的實例對象。實際上,甚至泛型類擴展Throwable都是不合法的;T extends Exception(Throwable)
Catch字句中不能使用類型變量;catch(T e)Error;
PS:可以消除已檢查異常的檢查:
Java異常處理的一個基本原則:必須為所有的已檢查異常提供一個處理器;不過可以利用泛型消除這個限制;
當你必須捕獲run中所有的已檢查異常,將其包裝到未檢查異常中,因為run方法聲明為不拋出任何已檢查異常;
不過在這里我么你沒有選擇這種“包裝”我們只是拋出異常,并“哄騙瀏覽器”讓它認為這不會一個已檢查異常;
通過使用泛型類,擦除,和@SuppertWarning標注就可以消除java類型系統(tǒng)的部分限制
<8>注意擦除后的沖突:
當泛型類型被擦除時,無法創(chuàng)建引發(fā)沖突的條件,如:
在某個泛型類中添加equals()方法,當我們給這個泛型類中間穿具體的類型時:
進過類型擦除之后equals(String o)方法變?yōu)閑quals(Object o);
所以此時從概念上講:他有兩個equals方法:
Boolean equals(String)
Boolean equals(Object)和Object.Equals(Object)方法沖突;
補救方法就是重命名引發(fā)錯誤的方法
另外一個原則:“想要支持擦除的轉(zhuǎn)換,就需要強行限制一個類或類型變量不能同時成為兩個接口類型的子類”而這兩個接口是同一個接口的不同參數(shù)化;
例如下面的代碼就是非法的
Class Calendar implements Comparable<Calendar>{…}
Class GregorianCalendar extends Calendar implements Comparable< GregorianCalendar>(Error)
GregorianCalendar會事先Comparable<Calendar>和Comparable<GregorianCalendar>
這是同一接口的不同參數(shù)化;這一限制和類型擦柱的關(guān)系不是十分明確;下列的非泛型的版本就是合法的;
原因:有可能與合成的橋方法產(chǎn)生沖突。實現(xiàn)了Comparable<x>的類可以獲得一個橋方法;
Public int comparaTo(Object other){
return compareTo(X) other
};
對于不同類型的;不能有兩個這樣的方法;
<9>泛型類型的繼承規(guī)則:
- 例如:Employee和Manager。Pair<Manager>和Pair<Employee>之間沒有任何關(guān)系;
無論S和T有什么關(guān)系,通常Pair<S>和Pair<T>之間都不會有什么聯(lián)系;
繼承泛型類 - 子類不是泛型類:需要給父類傳遞類型常量
當給父類傳遞的類型常量為String時,那么在父類中所有T都會被String替換!
- 子類是泛型類:可以給父類傳遞類型常量,也可以傳遞類型變量
3:通配符類型:
為了解決固定的泛型類型使用的不便利:java的設計者們發(fā)明了一種巧妙的(仍然是安全的)“解決方案”:通配符類型;
<1>:子類通配符:
例如:Pair<? Extends Employee>
表示任何泛型Pair類型,它的類型參數(shù)是Employee的子類;如Pair<Manager>
例子:
Pair<Manager> managerBuddies = new
Pair<>(ceo,cfo);
Pair<? Extends employee>
wildcardBuddies = managerBuddies;
wildcardBuddies.setFirst(lowlyEmployee);//error;
這可能不會以你破壞,對setFirst的調(diào)用有一個類型錯誤;
當你調(diào)用setFirst(? Extends Employee)編譯器僅僅知道是某個Employee的子類型,但是不知道具體是什么類型;它拒絕傳遞任何的類型。
但是使用getFirst就不存在這個問題;
這就是引入限定的統(tǒng)配符的關(guān)鍵之處;
<2>:通配符的超類型限定:
通配符限定和類型變量限定十分類似;但是還有一個附加能力,即可以指定一個超類型限定(supertype bound)
? super Manager設置類型的下限;
這個統(tǒng)配符限制為Manager的所有超類型;帶有超類型限定的通配符的行為和12.8接講的正好相反,可以為方法提供參數(shù),但是不能使用返回值。
例如:
Pair<? Super Manager>
Void setFirst(? Super Manager)
? super Manager getFirst();
編譯器不知道setFirst方法的確切類型,但是可以調(diào)用任意Manager對象(或其子類型,例如Executive)調(diào)用它,而不能使用Employee對象調(diào)用它,然而,如果調(diào)用getFirst,返回的對象類型就不能的都保證;只能將它的值賦給Object;
直觀的講,帶有超類型的限定的通配符可以向泛型對象寫入,帶有子類型限定的通配符可以從泛型對象中讀??;
public void set(List<? extends Number> list){
//list.add(new Integer(1));
//可以獲取泛型參數(shù)類型的值,但是不能向里面添加值
Number i =list.get(0);
}
public void set1(List<? super Integer> list){
list.add(new Integer(1));
//不可以獲取泛型參數(shù)類型的值,但是能向里面添加值
//Integer i = list.get(0);
}
超類型限定的另外一種應用:Comparable接口本身就是一個泛型類型;
Public interface Comparable<T>{
Public intcompareTo(T other);
}
當有一個具體的類型傳入其中時,相應的泛型類型就會被自動轉(zhuǎn)換成傳入的類型;se5.0之前。Other是一個Object并且這個方法的實現(xiàn)需要強制類型轉(zhuǎn)換;
<3>:無限定的通配符:
還可以使用無限定的通配符;例如Pair<?>.
4:泛型和反射
<1>:使用Class<T>參數(shù)進行類型匹配
Public static <T> Pair<T> makePair(Class<T> c) throws
InstantiationException,IllegalAccessException{
Return new Pair<>(c.newInstance(),c.newInstance());
}
調(diào)用:makePair(Emloyee.class)
<2>虛擬機中的泛型類型信息:
Java泛型的卓越特性之一就是在虛擬機中的類型擦除;雖然擦除了,但是在類中仍然會保留一些泛型祖先的微弱記憶;
在java虛擬機中,需要重新構(gòu)造是實現(xiàn)者聲明的泛型類,以及方法中的所有內(nèi)容;(通過反射去獲得用戶傳遞的類型的一些信息)不會知道特定對象或者方法調(diào)用,以及如何解釋類型采參數(shù);
為了表達泛型類型的聲明,java.lang.reflect包中提供了一個新的接口Type,這個接口包含了下列子類型的聲明:
Class類,描述具體的類型
TypeVariable接口
描述類型變量(如T extends Comparable<? Super T>)
WildcardType接口
描述通配符(如?Super T)
parameterizedType接口,描述泛型類或者接口類型(如Comparable <? Super T>)
GenericArrayType接口,描述泛型數(shù)組(如T[]);