Java基礎(chǔ)-泛型

什么是泛型

泛型程序設(shè)計就是為了讓一段代碼能夠被很多不同類型的對象所重用。Java提供的ArrayList就使用了泛型刊懈,使得該類能夠存儲多種不同的類型對象把将,比如ArrayList<String>保存String對象夷恍、ArrayList<Integer>保存Integer對象童擎。這里<String><Integer>中被尖括號包起來的就是類型參數(shù)苏章,類型參數(shù)表明了該泛型的實例對象中元素的類型寂嘉。

使用泛型

我們可以定義泛型類和泛型方法奏瞬,下面是兩個例子。

泛型類

泛型類就是具有一個或多個泛型變量的類泉孩。下面是一個泛型類的例子硼端。例子中T即為泛型變量,泛型變量使用的是大寫形式寓搬,一般有E表示集合的元素類型珍昨,KV表示鍵值對的類型、T句喷、U镣典、S表示任意類型。使用了泛型變量后唾琼,就可以在類定義中使用T來代表方法的參數(shù)兄春、方法的返回類型和變量的類型。

public class Pair<T>{
  private T first;
  private T second;
  public void setFirst(T first){...}
  public T getFirst(){...}
}
泛型方法

我們可以不定義泛型類锡溯,而在一個具體的類定義中使用泛型方法赶舆。下面是一個泛型方法的例子。泛型方法的泛型變量放置在修飾符的后面祭饭,返回類型的前面芜茵。在調(diào)用泛型方法的時候可以顯示的說明具體類型,也可以在有足夠信息讓編譯器推斷出類型的時候省略類型參數(shù)(當(dāng)然倡蝙,如果信息不夠就會拋出異常)夕晓。

class ArrayAlg{
  public static <T> T getMiddle(T... a){}
}

String middle=ArrayAlg.<String>getMiddle("A","B","C");
泛型變量

在定義泛型類或泛型方法的時候,可以使用的泛型變量不僅僅上上面所提到的T悠咱、E等,還可以通過extends關(guān)鍵詞來限定征炼,比如使用<T extends Comparable>來表明TComparable這個限定類型的子類型析既,或者可以粗略的理解為其派生類,不過這個例子里并不是說子類型必須是限定類型的派生類谆奥,也可以是實現(xiàn)了限定類型接口的子類型眼坏。
當(dāng)限定類型有多個的時候,可以使用&來進行連接酸些,比如<T extends Comparable & Seriablizable>宰译。

虛擬機中的泛型代碼

虛擬機沒有泛型類型對象,所有對象都屬于普通類魄懂,因此泛型在編譯后會被替換為原始類型沿侈。

類型擦除

泛型信息只存在于代碼編譯階段,在進入 JVM 之前市栗,與泛型相關(guān)的信息會被擦除掉缀拭,專業(yè)術(shù)語叫做類型擦除咳短。在定義一個泛型類型的時候,都自動提供了一個相應(yīng)的原始類型蛛淋,比如在泛型類和泛型方法的例子中原始類型為Object咙好,在使用了限定類型的時候原始類型就為限定類型。這個過程就是擦除類型變量褐荷,將其替換為限定類型勾效。對于上面的ArrayAlg被擦除后的原始類型如下。

public class ArrayAlg{
  public statac Object getMiddle(Object... a){}
}

在類型擦除后叛甫,在調(diào)用的時候?qū)l(fā)生類型轉(zhuǎn)換层宫。比如String middle=ArrayAlg.<String>getMiddle("A","B","C");,在實際執(zhí)行的時候會先調(diào)用原始類型的getMiddle()然后再將Object轉(zhuǎn)換為String類型返回合溺。

在對泛型進行類型擦除后卒密,可能會出現(xiàn)兩個復(fù)雜的問題。

  1. 兩個Setter
    上述的Pair<T>被擦除后棠赛,其setter方法將會變?yōu)?code>public void setFirst(Object first)哮奇。當(dāng)有一個類繼承了Pair<LocalDate>類并重寫了setFirst()的時候,將會出現(xiàn)兩個setFirst()方法(參數(shù)類型不同)睛约。
public class DateInterval extends Pair<LocalData>{
  public void setFirst(LocalData first){...}
}

//被擦除后的結(jié)果
public class DateInterval extends Pair{
  //派生類中實現(xiàn)的
  public void setFirst(LocalData first){...}
  //Pair中繼承的
  public void setFirst(Object first){...}
}

該類被擦除后的代碼如上鼎俘,此時Pair中原來的setFirst(T first)就變成了setFirst(Object first),與DateInterval中定義的setter方法參數(shù)不同辩涝,因此DateInterval中就出現(xiàn)了兩個setFirst方法贸伐。
為了解決這個問題,我們可以讓編譯器自行決定使用最適合的方法怔揩,或者使用一個橋方法重寫繼承的setter捉邢。

public void setFirst(Object first){
  setFirst((LocalData) first);
}
  1. 兩個getter
    兩個getter方法與上面的問題相似,當(dāng)派生類重寫了getFirst()就會出現(xiàn)兩個getter方法(返回類型不同)商膊,一個返回Object伏伐,一個返回LocalData,這個在Java編碼中是不允許的晕拆。這個問題我們不用過多操心藐翎,虛擬機能夠自行處理這個問題。
調(diào)用遺留代碼

假設(shè)我們實現(xiàn)了一個JSlider類实幕,通過setLabelTable(Dictionary table)來設(shè)置JSlider標簽吝镣,這里Dictionary是原始類型。如果我們通過Dictionary<Integer,Component>實例化了一個泛型類Dictionary昆庇,再調(diào)用setter方法末贾,編譯器將會進行警告,因為編譯器無法確定setter方法會對Dictionary對象做什么操作凰锡,可能會將Integer進行替換未舟,使得關(guān)鍵字不再是Integer圈暗。這種情況下,我們可以通過@SuppressWarnings("unchecked")進行標注裕膀,關(guān)閉對方法中代碼的檢查员串。

泛型的約束與限制

在使用泛型的時候,有很多限制需要注意昼扛,其中大多數(shù)都是由類型擦除引起的寸齐。

不能使用基本類型實例化類型參數(shù)

沒有Pair<int>,只有Pair<Integer>抄谐,這是因為Pair類含有Object類型的域渺鹦,而Object不能存儲int值。

運行時類型查詢只適用于原始類型

在虛擬機中泛型被擦除為原始類型蛹含,因此類型查詢只返回原始類型毅厚。

  • if(a instanceof Pair<String>)//error實際上只測試a是否為任意類型的一個Pair
  • getClass也總是返回原始類型浦箱,if(stringPair.getClass()==employeePair.getClass())// true吸耿。
  • 在強制類型轉(zhuǎn)換時,也會拋出錯誤酷窥,Pair<String> p=(Pair<String>)a;//Warning-can only test that a is a Pair咽安。
不能創(chuàng)建參數(shù)化類型的數(shù)組

不能實例化參數(shù)化類型的數(shù)組,例如Pair<String>[] table=new Pair<String>[10];//error蓬推。在這種情況下妆棒,擦除之后table的類型實際為Pair[],程序員可以將其轉(zhuǎn)換為Object[]沸伏,即Object[] objArray= table;糕珊。這樣轉(zhuǎn)換后,如果試圖存儲除了Pair的其他類型如objArray[0]="string";毅糟,將會得到錯誤Error-component type is Pair放接。因此,無法差un關(guān)鍵參數(shù)化類型的數(shù)組留特。
我們可以聲明通配類型的數(shù)組,然后進行類型轉(zhuǎn)換來通過編譯玛瘸,Pair<String>[] table=(Pair<String>[])new Pair<?>[10];蜕青。這種方法是不安全的,如果往table中存儲Pair<Employee>是可以成功的糊渊,但是對table[0].getFirst()調(diào)用一個String將會報出錯誤右核。

Varargs警告

在調(diào)用一個參數(shù)個數(shù)可變的函數(shù)時,如public static <T> void addAll(Collection<T> coll,T... ts)渺绒,ts實際上是一個數(shù)組贺喝,包含所有的實參菱鸥。上面我們提到過,無法創(chuàng)建參數(shù)化類型的數(shù)組躏鱼,在這里Java虛擬機必須創(chuàng)建一個Pair<T>數(shù)組氮采。對于這種情況,我們在調(diào)用的時候只會得到一個警告染苛,我們可以通過@SupressWarning("unchecked")@SafeVarargs標注關(guān)閉方法中代碼的檢測鹊漠。

不能實例化類型變量

在泛型類中或泛型方法中,不能使用像new T(...)茶行、T.class這樣的表達式來實例化類型變量躯概,因為類型變量被擦除后會變成Object對象,而本意是不希望掉用new Object()的畔师。下面這個例子就是非法的娶靡。

public class Pair<T>{
  public Pair(){
    first=new T();
  }
}

解決辦法是讓調(diào)用者提供一個構(gòu)造器的表達方式。

public static <T> Pair<T> makePair(Supplier<T> constr){
  return new Pair<>(constr.get(),constr.get());
}

Pair<String> p=Pair.makePair(String::new);
不能構(gòu)造泛型數(shù)組

在泛型類或方法中不能構(gòu)造泛型數(shù)組的原因是類型擦除會使得永遠創(chuàng)建相同類型的數(shù)組看锉。比如T[] array=new T[2]姿锭,實際上會永遠創(chuàng)建原始類型的數(shù)組。
如果創(chuàng)建該數(shù)組只是為了作為一個私有變量操作度陆,那么可以創(chuàng)建Object數(shù)組艾凯,然后在調(diào)用的時候再進行類型轉(zhuǎn)換。

public class ArrayList<E>{
  private Object[] elements;
  public ArrayList(){
    elements=(E[]) new Object[10];
  }
  public E get(int n){
    return (E) elements[n];
  }
}

這種方法不是萬能的懂傀,當(dāng)限定類型不能通過Object強制轉(zhuǎn)換得到時趾诗,將會發(fā)生ClassCastException異常。這時候的解決辦法就是提供一個數(shù)組構(gòu)造器表達式蹬蚁。

public static <T extends Comparable> T[] minmax(IntFunction<T[]> constr,T... a){
  T[] mm=constr.apply(2);
  ...
}

String[] ss=ArrayAlg.minmax(String[]::new,"A","B","C");
泛型類的靜態(tài)上下文中類型變量無效

不能在靜態(tài)域或方法中引用類型變量恃泪。

public class Singletion<T>{
  private static T singletonIns;//error
  public static T getSingleIns(){//error
    if(singletonIns==null) construct one;
    return singletonIns;
  }
}
不能拋出或捕獲泛型類的實例

不能拋出也不能捕獲泛型類對象,泛型類擴展Throwable也是不合法的犀斋。

public static <T extends Throwable> void doWork(Class<T> t){
  try{}
  catch(T e){}//error-cant catch type variable
}

不過贝乎,在異常規(guī)范中使用類型變量是允許的。(叽粹?)

public static <T extends Throwable> void doWork(T t) throws T{
  try{}
  catch(Throwable realCause){
    t.initCause(realCause);
    throw t;


  }
}
可以消除對受查異常的檢查
注意擦除后的沖突

當(dāng)泛型類型擦除時览效,可能會導(dǎo)致一些沖突。

  1. 方法沖突
    在下面這個例子中虫几,Pair<T>類中實現(xiàn)了euqals(T value)方法锤灿,在類型擦除后為euqals(Object value)方法,與從Object繼承的euqals(Object value)方法同名沖突辆脸。
public Pair<T>{
  public boolean euqals(T value){
    //Name clash:The method equals(T) of type Generics<T> has the same erasure as equals(Object) of type Object but does not override it
    return first.equals(value)&&second.euqals(value);
  }
}
  1. 類沖突
    泛型規(guī)范說明中有提到:“想要支持擦除的轉(zhuǎn)換但校,就需要強行限制一個類或類型變量不能同成為兩個接口類型的子類,而這兩個接口是同一接口的不同參數(shù)化啡氢。”例如下面的代碼就是非法的状囱。
class Employee implements Comparable<Employee>{}
class Manager extends Employee implements Comparable<Manager>{}//error

泛型類型的繼承規(guī)則

考慮一個類和其子類EmployeeManager术裸,Pair<Employee>卻與Pair<Manager>沒有任何關(guān)系,其之間沒有繼承的關(guān)系亭枷。也就是說無論ST有什么聯(lián)系袭艺,Pair<S>Pair<T>都沒有什么聯(lián)系。

泛型_繼承規(guī)則.png

通配符類型

上面說明了無論ST有什么聯(lián)系奶栖,Pair<S>Pair<T>都沒有什么聯(lián)系匹表。但是我們在實際使用的時候,并不希望Pair<Employee>不能夠存儲Pair<Manager>宣鄙,否則要分類存儲袍镀,變向地增加了代碼的繁瑣程度。Java中提供了通配符來解決這個問題冻晤。

通配符概念

首先要分清楚通配符與類型變量的區(qū)別苇羡。類型變量用于在定義泛型類或泛型方法的時候使用,而通配符是在使用泛型的時候來對泛型起限制作用鼻弧。
使用了通配符后设江,允許泛型實例的變量類型發(fā)生變化,比如Pair<? extends Employee>表示任何泛型Pair類型攘轩,它的類型參數(shù)是Employee的子類叉存,不再僅僅局限于Employee。這樣這個Pair就既可以存儲Manager又可以存儲Employee了度帮。
但是這里存在一個細節(jié)問題歼捏,對于下面這個例子,setter方法會出現(xiàn)編譯錯誤笨篷,而getter方法不會瞳秽。這是因為setter方法只說明了參數(shù)是Employee的派生類,但是不知道具體是什么類型率翅,但是getter方法知道返回類型是Employee的派生類练俐,可以將其派生類復(fù)制給Employee的引用。

Pair<Manager> managerBuddies=new Pair<>(ceo,cfo);
Pair<? extends Employee> wildcardBuddies=managerBuddies;//ok
wildcardBuddies.setFirst(lowlyEmployee);//compile-time error

? extends Employee getFirst();
void setFirst(? extends Employee);//error
通配符超類型限定

除了上述的<? extends Employee>表示Employee的派生類類型參數(shù)外冕臭,還可以通過<? supper Employee>表示Employee的超類類型參數(shù)腺晾。
同樣,使用了超類類型限定后辜贵,無法使用getter方法而可以使用setter方法丘喻。

? supperEmployee getFirst();//error
void setFirst(? supperEmployee);
無限定通配符

可以使用無限定的通配符,Pair<?>來表示存儲任何類型念颈。這個和原始類型的Pair類型有較大的區(qū)別,使用該通配符后無法再使用setter方法连霉。
無限定通配符通常被用來進行簡單的操作榴芳,比如下面的判斷是否為空引用嗡靡。

public static boolean hasNulls(Pair<?> P){
  return p.getFirst()==null || p.getSecond()==null;
}
通配符捕獲

對于一個交換元素的方法public static void swap(Pair<?> p),我們無法在方法中使用? t=p.getFirst()來初始化變量窟感,也就是說無法使用?來表示類型讨彼。在這種情況下,我們需要通過一個輔助方法來解決柿祈。

public static <T> void swapHelper(Pair<T> p){
  T t=p.getFirst();
  p.setFirst(p.getSecond());
  p.setSecond(t);
}

public static void swap(Pair<?> p){swapHelper(p);}

當(dāng)然哈误,這里也可以直接使用泛型來解決這個問題。但是在想要使用supper關(guān)鍵字而不得不使用通配符的時候躏嚎,該方法就是必須了蜜自。

public static void maxminBonus(Manager[] a,Pair<? supper Manager> result){
  minmaxBonus(a,result);
  PairAlg.swap(result);//這里就需要一個swapHelper
}                                                                                

反射和泛型

泛型由于會發(fā)生類型擦除,因此在使用反射的時候得不到太多信息卢佣,下面將介紹用反射能夠得到的信息重荠。

泛型Class類

Class類是泛型的,String.class實際上是一個Class<String>類的對象虚茶。下面是Class<T>中使用了類型參數(shù)的方法戈鲁。

T newInstance();
T cast(Object obj);
T[] getEnumConstants();
Class<? super T> getSuperclass();
Constructor<T> getConstructor(Class... parameterTypes);
Constructor<T> getDeclaredConstructor(Class... parameterTypes);
使用Class<T>參數(shù)進行類型匹配
public static <T> Pair<T> makePair(Class<T> c) throws InstantiationException,IllegalAccessException{
  return new Pair<>(c.newInsatance(),c.newInstance());
}
虛擬機中的泛型類型信息

虛擬機中被擦除的類仍能夠保持一些泛型祖先的信息。對于方法public static Comparable min(Comparable[] a)嘹叫,這是一個泛型方法public static <T extends Comparable<? super T>> T min(T[] a)的擦除婆殿。我們可以通過反射API來確定:

  • 這個泛型方法有一個叫做T的類型參數(shù)
  • 這個類型參數(shù)有一個子類型限定,其自身又是一個泛型類型
  • 這個限定類型有一個通配符參數(shù)
  • 這個通配符參數(shù)有一個超類型限定
  • 這個泛型方法有一個泛型數(shù)組參數(shù)
    java.lang.reflect包中提供了Type接口罩扇,包含下列子類型:
  • Class類婆芦,描述具體類型
  • TypeVariable接口,描述類型變量(如T extends Comparable<? super T>
  • WildcardType接口暮蹂,描述通配符(如? super T
  • ParameterizedType接口寞缝,描述泛型類或接口類型(如Comparable<? super T>
  • GenericArrayType接口,描述泛型數(shù)組(如T[]
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末仰泻,一起剝皮案震驚了整個濱河市荆陆,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌集侯,老刑警劉巖被啼,帶你破解...
    沈念sama閱讀 218,122評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異棠枉,居然都是意外死亡浓体,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評論 3 395
  • 文/潘曉璐 我一進店門辈讶,熙熙樓的掌柜王于貴愁眉苦臉地迎上來命浴,“玉大人,你說我怎么就攤上這事∩校” “怎么了媳溺?”我有些...
    開封第一講書人閱讀 164,491評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長碍讯。 經(jīng)常有香客問我悬蔽,道長,這世上最難降的妖魔是什么捉兴? 我笑而不...
    開封第一講書人閱讀 58,636評論 1 293
  • 正文 為了忘掉前任蝎困,我火速辦了婚禮,結(jié)果婚禮上倍啥,老公的妹妹穿的比我還像新娘禾乘。我一直安慰自己,他們只是感情好逗栽,可當(dāng)我...
    茶點故事閱讀 67,676評論 6 392
  • 文/花漫 我一把揭開白布盖袭。 她就那樣靜靜地躺著,像睡著了一般彼宠。 火紅的嫁衣襯著肌膚如雪鳄虱。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,541評論 1 305
  • 那天凭峡,我揣著相機與錄音拙已,去河邊找鬼。 笑死摧冀,一個胖子當(dāng)著我的面吹牛倍踪,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播索昂,決...
    沈念sama閱讀 40,292評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼建车,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了椒惨?” 一聲冷哼從身側(cè)響起缤至,我...
    開封第一講書人閱讀 39,211評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎康谆,沒想到半個月后领斥,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,655評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡沃暗,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,846評論 3 336
  • 正文 我和宋清朗相戀三年月洛,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片孽锥。...
    茶點故事閱讀 39,965評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡嚼黔,死狀恐怖细层,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情唬涧,我是刑警寧澤今艺,帶...
    沈念sama閱讀 35,684評論 5 347
  • 正文 年R本政府宣布,位于F島的核電站爵卒,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏撵彻。R本人自食惡果不足惜钓株,卻給世界環(huán)境...
    茶點故事閱讀 41,295評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望陌僵。 院中可真熱鬧轴合,春花似錦、人聲如沸碗短。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽偎谁。三九已至总滩,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間巡雨,已是汗流浹背闰渔。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評論 1 269
  • 我被黑心中介騙來泰國打工米间, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留拉庵,地道東北人。 一個月前我還...
    沈念sama閱讀 48,126評論 3 370
  • 正文 我出身青樓拍顷,卻偏偏與公主長得像正蛙,于是被迫代替她去往敵國和親督弓。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,914評論 2 355