淺談Java中的泛型

引言:泛型一直是困擾自己的一個(gè)難題,但是泛型有時(shí)一個(gè)面試時(shí)老生常談的問(wèn)題;今天作者就通過(guò)查閱相關(guān)資料簡(jiǎn)單談?wù)勛约簩?duì)泛型的理解;

一:什么是泛型:

  • 泛型就是參數(shù)化類型,就是所操作的數(shù)據(jù)類型被指定為一個(gè)參數(shù),這種類型可以在類接口和方法中創(chuàng)建,分別稱之為泛型類,泛型接口,泛型方法

二:為什么要使用泛型:

  • 使用泛型可以讓我們編寫的代碼可以被很多類型的對(duì)象重用;
    例如我們不希望為String和file對(duì)象分別設(shè)計(jì)不同的類;實(shí)際上我們也并不需要這么做;
    就拿ArrayList類舉例:可以聚集任何類型的對(duì)象;Javase5.0之前是使用繼承實(shí)現(xiàn)的;ArrayList類只維護(hù)一個(gè)Object引用的數(shù)組;
Public class ArrayList{
    Public Object get(int i){
    }
  Public void add(Object o){
  }
  Public Object[] elementData;
  }

問(wèn)題1:獲取值必須進(jìn)行強(qiáng)制類型轉(zhuǎn)換;
問(wèn)題2:沒(méi)有錯(cuò)誤檢查可以向數(shù)組列表中添加任何參數(shù);
針對(duì)以上兩種情況泛型提出了更好的解決方案;類型參數(shù);(type parameters)使我們?cè)O(shè)計(jì)的程序具有更好的可讀性和安全性

三:泛型都有哪些?

  • 1:泛型類:

    就是一個(gè)或者多個(gè)具有類型變量的類;如下:
 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類引入了一個(gè)類型變量T,將其放在"<>"里面;并且將其放在類名的后面,泛型類也可以有多個(gè)類型變量;
可以將泛型類看做是普通類的工廠;

  • 2:泛型方法:

    帶有類型參數(shù)的簡(jiǎn)單方法;
    泛型方法可以定義在普通類里面也可以定義在泛型類里面;
    所謂的泛型方法要么就是返回值是一個(gè)泛型,要么就是參數(shù)是泛型;
    class ArrayAlg{
      public static <T> T getMiddle(T[] a){
        return a[a.length/2]
      }
    }
    
  • 3:泛型接口

    就是一個(gè)或者多個(gè)具有類型變量的接口
    pulbic interface Collection<E> extends Iterable<E> {
    Iterator<E> iterator();
    }

四:泛型使用方法

  • 1:泛型代碼和虛擬機(jī)

    注意虛擬機(jī)中沒(méi)有泛型類型的對(duì)象,所有的對(duì)象都屬于普通類;
    無(wú)論什么時(shí)候定義泛型,java虛擬機(jī)都會(huì)自動(dòng)提供一個(gè)相應(yīng)的原始類型(raw type);原始類型的名字就是刪除泛型參數(shù)之后的泛型類型名;擦除類型變量,并替換為限定類型,如果沒(méi)有限定類型的變量就替換成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>:翻譯泛型表達(dá)式
當(dāng)程序調(diào)用泛型方法時(shí),如果擦除返回類型,編譯器就會(huì)插入強(qiáng)制類型轉(zhuǎn)換;eg:
  Pair<Employee> buddies = ...;
  Employee buudy = buudies.getFirst();
  //這里擦?xí)鴊etFirst的返回類型后將返回Object類型。編譯器自動(dòng)插入Employee的強(qiáng)制類型轉(zhuǎn)換。也就是說(shuō)編譯器把這個(gè)方法翻譯為兩條虛擬機(jī)指令:
  A:對(duì)原始方法Pair.getFirst的調(diào)用
  B:將返回的Object類型強(qiáng)制轉(zhuǎn)換為Employee類型
<2>:翻譯泛型方法
public static <T extends Comparable> T min(T[] a)是一個(gè)完整的方法族,擦除類型之后只剩下一個(gè)方法了:

 public static Comparable min(Comparable[] a)
 此時(shí)僅僅留下限定類型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); 
             }
         }
     }

此時(shí)存在另一個(gè)從Pair中繼承來(lái)的setSecond方法,即
public void setSecond(Object second)
這個(gè)方法顯然和setSecond(Date date)不是同一個(gè)方法;
考慮下面的代碼:

DateInterval interval = new DateInterval(...);
 Pair<Date> pair = interval;
 pair.setSecond(aDate);

這里我們希望setSecond的調(diào)用具有多態(tài)性;并調(diào)用最合適的那個(gè)方法;希望pair調(diào)用DateInterval.setSecond方法;但此時(shí)類型擦?xí)投鄳B(tài)已經(jīng)發(fā)生了沖突:想要解決這個(gè)問(wèn)題我們需要建立一個(gè)橋方法;

public void setSecond(Object second){
  setSecond((Date) second)
}

總之:記住有關(guān)java泛型轉(zhuǎn)換的事實(shí):
A:虛擬機(jī)中沒(méi)有泛型,只有普通方法和普通類;
B:所有的類型參數(shù)都用他們的限定類型替換
C:橋方法被合成用來(lái)保持多態(tài);
D:為保持類型安全性,必要時(shí)插入強(qiáng)制類型轉(zhuǎn)換。

2:約束和局限性:

使用java中的泛型時(shí)需要考慮一些限制,大多是限制都是由于類型擦除引起的;

<1>:不能用基本類型實(shí)例化類型參數(shù)
沒(méi)有Pair<double>只有Pair<Double>
<2>:運(yùn)行時(shí)類查詢只適用于原始類型
虛擬機(jī)中的對(duì)象總有一個(gè)特定的非泛型類型。因此所有的類型查詢只產(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ù)組;當(dāng)我們向一個(gè)參數(shù)個(gè)數(shù)可變的方法 傳遞一個(gè)泛型類型的實(shí)例:
eg:

public static <T> void addAll(Collection<T> coll,T...ts){
  for(T t:ts)coll.add(t);
}
實(shí)際上ts是一個(gè)數(shù)組,包含提供的所有實(shí)參。
Collection<Pair<String>> table = ...;
Pair<String> pair1 = ....;
Pair<String> pair2 = ....;
Pair<String> pair3 = ....;
addAll(table,pair1,pair2);
為了調(diào)用這個(gè)方法,Java虛擬機(jī)必須建立一個(gè)Pair<String>數(shù)組;違反前面的規(guī)定;但是這種情況并不會(huì)報(bào)告錯(cuò)誤僅僅會(huì)報(bào)告一個(gè)警告可以通過(guò)兩種方式去消除它:
A:為addAll方法添加標(biāo)注@SuppressWarning("unchecked").
B:@SafeVarargs直接標(biāo)志addAll方法
@SafeVarags
public static <T> void addAll(Collection<T> coll,T...ts)
#####<5>:不能實(shí)例化類型參數(shù)
new T(...),new T[]或者T.class都是不允許的;
但是可以通過(guò)反射加上一定的API便可實(shí)現(xiàn)泛型的實(shí)例化;
```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)

類型擦除會(huì)讓這個(gè)方法永遠(yuǎn)構(gòu)造Object[2]數(shù)組;
如果數(shù)組僅僅作為一個(gè)類的私有實(shí)例域,就可以將這個(gè)數(shù)組聲明為Object[],并在獲取元素時(shí)進(jìn)行類型轉(zhuǎn)換。例如ArrayList就是這樣實(shí)現(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];//假像:類型擦除會(huì)使其無(wú)法差距
  }
}

編譯時(shí)不會(huì)報(bào)錯(cuò),但是當(dāng)我們的程序執(zhí)行時(shí),當(dāng)我們把Object[]引用賦值給T[]時(shí)就會(huì)報(bào)錯(cuò);將會(huì)發(fā)生ClassCastException異常;
這種情況下我們可以利用反射:

Public static <T extends Comparable> T[] minmax(T…a){
  T[] mm = (T[])Array.newInstance(a.getClass().getComponentType(),2)
}
<6>泛型類的靜態(tài)上下文中類型變量無(wú)效

不能在靜態(tài)域或方法中引用類型變量(即泛型);(即被static修飾的域或者方法)

<7>:不能拋出或者捕獲泛型類的實(shí)例

既不能拋出也不能捕獲泛型類的實(shí)例對(duì)象。實(shí)際上,甚至泛型類擴(kuò)展Throwable都是不合法的;T extends Exception(Throwable)
Catch字句中不能使用類型變量;catch(T e)Error;
PS:可以消除已檢查異常的檢查:
Java異常處理的一個(gè)基本原則:必須為所有的已檢查異常提供一個(gè)處理器;不過(guò)可以利用泛型消除這個(gè)限制;
當(dāng)你必須捕獲run中所有的已檢查異常,將其包裝到未檢查異常中,因?yàn)閞un方法聲明為不拋出任何已檢查異常;
不過(guò)在這里我么你沒(méi)有選擇這種“包裝”我們只是拋出異常,并“哄騙瀏覽器”讓它認(rèn)為這不會(huì)一個(gè)已檢查異常;
通過(guò)使用泛型類,擦除,和@SuppertWarning標(biāo)注就可以消除java類型系統(tǒng)的部分限制

<8>注意擦除后的沖突:

當(dāng)泛型類型被擦除時(shí),無(wú)法創(chuàng)建引發(fā)沖突的條件,如:
在某個(gè)泛型類中添加equals()方法,當(dāng)我們給這個(gè)泛型類中間穿具體的類型時(shí):
進(jìn)過(guò)類型擦除之后equals(String o)方法變?yōu)閑quals(Object o);
所以此時(shí)從概念上講:他有兩個(gè)equals方法:
Boolean equals(String)
Boolean equals(Object)和Object.Equals(Object)方法沖突;
補(bǔ)救方法就是重命名引發(fā)錯(cuò)誤的方法
另外一個(gè)原則:“想要支持擦除的轉(zhuǎn)換,就需要強(qiáng)行限制一個(gè)類或類型變量不能同時(shí)成為兩個(gè)接口類型的子類”而這兩個(gè)接口是同一個(gè)接口的不同參數(shù)化;
例如下面的代碼就是非法的

Class Calendar implements Comparable<Calendar>{…}
Class GregorianCalendar extends Calendar implements Comparable< GregorianCalendar>(Error)
GregorianCalendar會(huì)事先Comparable<Calendar>和Comparable<GregorianCalendar>

這是同一接口的不同參數(shù)化;這一限制和類型擦柱的關(guān)系不是十分明確;下列的非泛型的版本就是合法的;
原因:有可能與合成的橋方法產(chǎn)生沖突。實(shí)現(xiàn)了Comparable<x>的類可以獲得一個(gè)橋方法;

Public int comparaTo(Object other){
  return compareTo(X) other
};

對(duì)于不同類型的;不能有兩個(gè)這樣的方法;

<9>泛型類型的繼承規(guī)則:
  • 例如:Employee和Manager。Pair<Manager>和Pair<Employee>之間沒(méi)有任何關(guān)系;
    無(wú)論S和T有什么關(guān)系,通常Pair<S>和Pair<T>之間都不會(huì)有什么聯(lián)系;
    繼承泛型類
  • 子類不是泛型類:需要給父類傳遞類型常量

    當(dāng)給父類傳遞的類型常量為String時(shí),那么在父類中所有T都會(huì)被String替換!

  • 子類是泛型類:可以給父類傳遞類型常量,也可以傳遞類型變量

3:通配符類型:

為了解決固定的泛型類型使用的不便利:java的設(shè)計(jì)者們發(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;
這可能不會(huì)以你破壞,對(duì)setFirst的調(diào)用有一個(gè)類型錯(cuò)誤;
當(dāng)你調(diào)用setFirst(? Extends Employee)編譯器僅僅知道是某個(gè)Employee的子類型,但是不知道具體是什么類型;它拒絕傳遞任何的類型。
但是使用getFirst就不存在這個(gè)問(wèn)題;
這就是引入限定的統(tǒng)配符的關(guān)鍵之處;

<2>:通配符的超類型限定:
   通配符限定和類型變量限定十分類似;但是還有一個(gè)附加能力,即可以指定一個(gè)超類型限定(supertype bound)
   ? super Manager設(shè)置類型的下限;
   這個(gè)統(tǒng)配符限制為Manager的所有超類型;帶有超類型限定的通配符的行為和12.8接講的正好相反,可以為方法提供參數(shù),但是不能使用返回值。
例如:
Pair<? Super Manager>
Void setFirst(? Super Manager)
? super Manager getFirst();

編譯器不知道setFirst方法的確切類型,但是可以調(diào)用任意Manager對(duì)象(或其子類型,例如Executive)調(diào)用它,而不能使用Employee對(duì)象調(diào)用它,然而,如果調(diào)用getFirst,返回的對(duì)象類型就不能的都保證;只能將它的值賦給Object;
直觀的講,帶有超類型的限定的通配符可以向泛型對(duì)象寫入,帶有子類型限定的通配符可以從泛型對(duì)象中讀??;

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);
    }

超類型限定的另外一種應(yīng)用:Comparable接口本身就是一個(gè)泛型類型;

Public interface Comparable<T>{
       Public intcompareTo(T other);
}

當(dāng)有一個(gè)具體的類型傳入其中時(shí),相應(yīng)的泛型類型就會(huì)被自動(dòng)轉(zhuǎn)換成傳入的類型;se5.0之前。Other是一個(gè)Object并且這個(gè)方法的實(shí)現(xiàn)需要強(qiáng)制類型轉(zhuǎn)換;

<3>:無(wú)限定的通配符:

還可以使用無(wú)限定的通配符;例如Pair<?>.

4:泛型和反射
<1>:使用Class<T>參數(shù)進(jìn)行類型匹配
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>虛擬機(jī)中的泛型類型信息:
   Java泛型的卓越特性之一就是在虛擬機(jī)中的類型擦除;雖然擦除了,但是在類中仍然會(huì)保留一些泛型祖先的微弱記憶;
   在java虛擬機(jī)中,需要重新構(gòu)造是實(shí)現(xiàn)者聲明的泛型類,以及方法中的所有內(nèi)容;(通過(guò)反射去獲得用戶傳遞的類型的一些信息)不會(huì)知道特定對(duì)象或者方法調(diào)用,以及如何解釋類型采參數(shù);

為了表達(dá)泛型類型的聲明,java.lang.reflect包中提供了一個(gè)新的接口Type,這個(gè)接口包含了下列子類型的聲明:
Class類,描述具體的類型
TypeVariable接口
描述類型變量(如T extends Comparable<? Super T>)
WildcardType接口
描述通配符(如?Super T)
parameterizedType接口,描述泛型類或者接口類型(如Comparable <? Super T>)
GenericArrayType接口,描述泛型數(shù)組(如T[]);

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容