Generic 范型 - type parameter

范型簡(jiǎn)介

從JDK 5.0開(kāi)始,范型作為一種新的擴(kuò)展被引入到了java語(yǔ)言中。

有了范型丐怯,我們可以對(duì)類(lèi)型(type=class+interface)進(jìn)行抽象灾而。最常見(jiàn)的例子是容器類(lèi)型耳高。

List myIntList = new LinkedList(); // 1
myIntList.add(new Integer(0)); // 2
Integer x = (Integer) myIntList.iterator().next(); // 3      

可以用范型對(duì)以上代碼進(jìn)行優(yōu)化:

List<Integer> 
    myIntList = new LinkedList<Integer>(); // 1'
myIntList.add(new Integer(0)); // 2'
Integer x = myIntList.iterator().next(); // 3'

優(yōu)化帶來(lái)兩點(diǎn)改進(jìn):

  1. 省去了造型(cast)的麻煩。
  2. 除了代碼上的整潔,范型還在compile-time保證了代碼的類(lèi)型正確。如果沒(méi)有范型箫踩,無(wú)法保證放入list的對(duì)象是Integer型。

定義簡(jiǎn)單的范型

從package java.util中摘錄下接口List和Iterator的定義:

public interface List <E> {
    void add(E x);
    Iterator<E> iterator();
}

public interface Iterator<E> {
    E next();
    boolean hasNext();
}

這里聲明了type parameter:E谭贪。Type parameters在范型的全部聲明中都可以用境钟,就像使用其他普通的類(lèi)型一樣。

調(diào)用范型的時(shí)候俭识,需要為type parameter E 指定一個(gè)真實(shí)的類(lèi)型變量【1】(又稱(chēng)為parameterized type)吱韭,例如:

List<Integer> myIntList = new LinkedList<Integer>();

可以想象List<Integer>是List的一個(gè)版本,在這個(gè)版本里面鱼的,所有的 type parameter (E)都被Integer替換了:

public interface IntegerList {
    void add(Integer x);
    Iterator<Integer> iterator();
}

這種想象很有幫助理盆,因?yàn)閜arameterized type的List<Integer> 確實(shí)包含了類(lèi)似的方法;但是也容易帶來(lái)誤導(dǎo)凑阶,因?yàn)槊看?code>調(diào)用范型并不會(huì)生成代碼的一個(gè)拷貝猿规,通過(guò)編譯,一個(gè)范型類(lèi)型的聲明只會(huì)編譯一次宙橱,生成一個(gè)class文件姨俩;每次調(diào)用范型,類(lèi)似于給一個(gè)方法傳入了一個(gè)argument师郑,只是這里傳入的是一個(gè)普通的類(lèi)型环葵。

【1】這里用的是argument,即傳給方法的值宝冕;區(qū)別parameter张遭,parameter是作為方法簽名的一部分,用于定義方法地梨。

范型和子類(lèi)型

假設(shè)Foo是Bar的子類(lèi)型(class或者interface)菊卷,G是一個(gè)范型類(lèi)型聲明,G<Foo> 不是G<Bar>的子類(lèi)型宝剖,這點(diǎn)有些反直覺(jué)洁闰。

wildcards 通配符

接著上一節(jié)的討論,假設(shè)Foo是Bar的子類(lèi)型(class或者interface)万细,G是一個(gè)范型類(lèi)型聲明扑眉,G<Foo> 不是G<Bar>的子類(lèi)型±党可是腰素,如果我們確實(shí)需要在G<Foo> 和G<Bar>之間建立父子關(guān)系呢?具體來(lái)說(shuō)仁烹,假設(shè)有以下一段代碼:

void printCollection(Collection c) {
    Iterator i = c.iterator();
    for (k = 0; k < c.size(); k++) {
        System.out.println(i.next());
    }
}

用范型對(duì)其進(jìn)行優(yōu)化耸弄,這里是一種錯(cuò)誤的方式:

void printCollection(Collection<Object> c) {
    for (Object e : c) {
        System.out.println(e);
    }
}

這樣寫(xiě)本身沒(méi)有錯(cuò)誤,但是他對(duì)Collection中元素的類(lèi)型進(jìn)行了限制卓缰,只能是Object计呈!那么,所有collection的超類(lèi)是神馬呢征唬?就是Collection<?>(讀作"collection of unknown")捌显,這個(gè)Collection的元素類(lèi)型可以任意匹配,总寒?被稱(chēng)作wildcard type扶歪。

void printCollection(Collection<?> c) {
    for (Object e : c) {
        System.out.println(e);
    }
}

嗯,不錯(cuò),現(xiàn)在我們可以從c中讀取出任意類(lèi)型的元素善镰∶萌可以,這樣一來(lái)炫欺,又出現(xiàn)了新的問(wèn)題:什么樣的元素可以放到c里面去呢乎完?答案是:任何類(lèi)型的元素都無(wú)法放到c里面去!因?yàn)闊o(wú)法知道c中的type parameter(也許寫(xiě)作E)是什么類(lèi)型品洛。

Bounded Wildcards 有界通配符

可能是考慮到?過(guò)于寬泛树姨,java引入了Bounded Wildcards,有界通配符桥状。假設(shè)有以下代碼:

public abstract class Shape {
    public abstract void draw(Canvas c);
}

public class Circle extends Shape {
    private int x, y, radius;
    public void draw(Canvas c) {
        ...
    }
}

public class Rectangle extends Shape {
    private int x, y, width, height;
    public void draw(Canvas c) {
        ...
    }
}

// These classes can be drawn on a canvas:
public class Canvas {
    public void draw(Shape s) {
        s.draw(this);
   }
}
// Assuming that they are represented as a list, 
// it would be convenient to have a method in Canvas that draws them all:
public void drawAll(List<Shape> shapes) {
    for (Shape s: shapes) {
        s.draw(this);
   }
}

看上去不錯(cuò)帽揪,但是問(wèn)題又來(lái)了,類(lèi)型方法drawAll的簽名參數(shù)中的ShapeCircle的超類(lèi)辅斟,盡管CircleShape的子類(lèi)转晰,但是List<Circle>不是List<Shape>的子類(lèi)。所以要想drawAll可以處理List<Circle>砾肺,可以將其定義為:

public void drawAll(List<? extends Shape> shapes) {
    ...
}

Bounded Wildcards 也面臨著?面臨的問(wèn)題挽霉,那就是他們都過(guò)于寬泛,因此無(wú)法
確定什么樣的元素可以放到集合里面:

public void addRectangle(List<? extends Shape> shapes) {
    // Compile-time error!
    shapes.add(0, new Rectangle());
}

范型方法

前面討論了范型type的聲明变汪,其實(shí)侠坎,同樣可以聲明范型方法:

static <T> void fromArrayToCollection(T[] a, Collection<T> c) {
    for (T o : a) {
        c.add(o); // Correct
    }
}

所謂范型方法,就是在方法簽名內(nèi)的修飾符和方法返回類(lèi)型之間裙盾,加入了type parameter实胸,例如<T>。在調(diào)用方法的時(shí)候番官,并不需要傳入type argument庐完,編譯器會(huì)根據(jù)actual argument的類(lèi)型推斷(infer)出type argument。

較之于范型類(lèi)型徘熔,范型方法的聲明要稍微復(fù)雜一些门躯。具體來(lái)說(shuō),范型方法包含返回值和若干parameter酷师,而他們之間可能會(huì)存在著類(lèi)型的依賴(lài)關(guān)系讶凉。而這種依賴(lài)關(guān)系就帶來(lái)一個(gè)問(wèn)題,什么時(shí)候應(yīng)該使用通配符山孔,什么時(shí)候應(yīng)該使用范型方法呢懂讯?
比如,查看JDK文檔:

interface Collection<E> {
    public boolean containsAll(Collection<?> c);
    public boolean addAll(Collection<? extends E> c);
}

為什么不寫(xiě)成:

interface Collection<E> {
    public <T> boolean containsAll(Collection<T> c);
    public <T extends E> boolean addAll(Collection<T> c);
    // Hey, type variables can have bounds too!
}

在containsAll 和 addAll中台颠,type parameter T 僅使用了1次褐望。返回值和其他parameter并不依賴(lài)于它,這種情況下,應(yīng)該使用通配符瘫里。只有當(dāng)返回值和parameter之間存在依賴(lài)的情況下实蔽,才應(yīng)該使用范型方法。例如:

class Collections {
    public static <T> void copy(List<T> dest, List<? extends T> src) {
    ...
}

范型是如何實(shí)現(xiàn)的

范型是通過(guò)編譯器對(duì)代碼的erasure轉(zhuǎn)換實(shí)現(xiàn)的减宣⊙涡耄可以把這一過(guò)程想象成source-to-source的翻譯。例如:

public String loophole(Integer x) {
    List<String> ys = new LinkedList<String>();
    List xs = ys;
    xs.add(x); // Compile-time unchecked warning
    return ys.iterator().next();
}

將被翻譯成:

public String loophole(Integer x) {
    List ys = new LinkedList;
    List xs = ys;
    xs.add(x); 
    return(String) ys.iterator().next(); // run time error
}

在第二段代碼中漆腌,我們從list中取出一個(gè)元素,并試圖通過(guò)將其cast(造型)把它當(dāng)成String處理阶冈,這里會(huì)得到一個(gè)ClassCastException闷尿。

因?yàn)樵诰幾g階段,編譯器對(duì)代碼進(jìn)行了erasure女坑,<>內(nèi)的一切都被刪除了填具,所以所有對(duì)范型類(lèi)型的調(diào)用(Invocations,或者說(shuō)實(shí)例)共享同一個(gè)run-time class匆骗,隨之而來(lái)的劳景,static變量和方法也被這些實(shí)例共享,所以在static方法中碉就,也無(wú)法引用type parameter盟广;同時(shí),Cast 和InstanceOf操作也就都失去了意義瓮钥。

Collection cs = new ArrayList<String>();
// Illegal.
if (cs instanceof Collection<String>) { ... }

// Unchecked warning,
Collection<String> cstr = (Collection<String>) cs;
//gives an unchecked warning, since this isn't something the runtime system is //going to check for you.

同理筋量,對(duì)于方法來(lái)說(shuō),type variables(<T>在方法中叫type variables碉熄,在類(lèi)型聲明中叫parameterized type)也不存在于run-time:

// Unchecked warning. 
<T> T badCast(T t, Object o) {
    return (T) o;
}

如何定義范型數(shù)組

private E[] elements = (E[]) new Object[10];
  • 數(shù)組和范型對(duì)類(lèi)型的檢查是不同的桨武。

對(duì)于數(shù)組來(lái)說(shuō),下面的語(yǔ)句是合法的:

Object[] arr = new String[10];

Object[] 是 String[]的超類(lèi)锈津,因?yàn)镺bject是String的超類(lèi)呀酸。然而,對(duì)于范型來(lái)說(shuō)琼梆,就沒(méi)有這樣的繼承關(guān)系性誉,因此,以下聲明無(wú)法通過(guò)編譯:

List<Object> list = new ArrayList<String>(); // Will not compile. generics are invariant.

java中引入范型叮叹,是為了在編譯階段強(qiáng)化類(lèi)型檢查艾栋。同時(shí),因?yàn)?code>type erasure蛉顽,范型也沒(méi)有runtime的任何信息蝗砾。所以,List<String> 只有靜態(tài)類(lèi)型的 List<String>,和一個(gè)動(dòng)態(tài)類(lèi)型 List悼粮。

但是闲勺,數(shù)組攜帶了runtime的類(lèi)型信息。在runtime扣猫,數(shù)組用Array Store Check來(lái)檢查將要插入的元素是否和真實(shí)的數(shù)組類(lèi)型兼容菜循。因此,以下代碼能很好的編譯申尤,但是由于Array Store Check癌幕,會(huì)在runtime失敗:

Object[] arr = new String[10];
arr[0] = new Integer(10);

回到范型昧穿,編譯器會(huì)提供編譯階段的檢查勺远,避免這種以這種方式創(chuàng)建索引,防止runtime的異常出現(xiàn)时鸵。

  • 那么胶逢,創(chuàng)建范型數(shù)組有什么問(wèn)題呢?
    創(chuàng)建元素的類(lèi)型是type parameter, parameterized type 或者bounded wildcard parameterized type的數(shù)組是type-unsafe的饰潜〕踝梗考慮如下代碼:
public <T> T[] getArray(int size) {
    T[] arr = new T[size];  // Suppose this was allowed for the time being.
    return arr;
}

在rumtime,T的類(lèi)型未知彭雾,實(shí)際上創(chuàng)建的數(shù)組是Object[]碟刺,因此在runtime,上面的方法像是:

public Object[] getArray(int size) {
    Object[] arr = new Object[size];
    return arr;
}

假設(shè)冠跷,有以下調(diào)用:

Integer[] arr = getArray(10);

這就是問(wèn)題南誊,這里將Object[] 指派給了一個(gè)Integer[]類(lèi)型的索引,這段代碼編譯沒(méi)有問(wèn)題蜜托,但是在runtime會(huì)失敗抄囚。因此,創(chuàng)建范型數(shù)組是不合法的橄务。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末幔托,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子蜂挪,更是在濱河造成了極大的恐慌重挑,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,839評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件棠涮,死亡現(xiàn)場(chǎng)離奇詭異谬哀,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)严肪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)史煎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)谦屑,“玉大人,你說(shuō)我怎么就攤上這事篇梭∏獬龋” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,116評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵恬偷,是天一觀的道長(zhǎng)悍手。 經(jīng)常有香客問(wèn)我,道長(zhǎng)袍患,這世上最難降的妖魔是什么坦康? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,371評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮协怒,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘孕暇。我一直安慰自己,他們只是感情好赤兴,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布妖滔。 她就那樣靜靜地躺著,像睡著了一般承二。 火紅的嫁衣襯著肌膚如雪亥鸠。 梳的紋絲不亂的頭發(fā)上识啦,一...
    開(kāi)封第一講書(shū)人閱讀 49,111評(píng)論 1 285
  • 那天齿税,我揣著相機(jī)與錄音礁凡,去河邊找鬼塞淹。 笑死,一個(gè)胖子當(dāng)著我的面吹牛每庆,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 38,416評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼苦丁,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼浸颓!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起旺拉,我...
    開(kāi)封第一講書(shū)人閱讀 37,053評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤产上,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后蛾狗,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體晋涣,經(jīng)...
    沈念sama閱讀 43,558評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評(píng)論 2 325
  • 正文 我和宋清朗相戀三年沉桌,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了谢鹊。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片算吩。...
    茶點(diǎn)故事閱讀 38,117評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖佃扼,靈堂內(nèi)的尸體忽然破棺而出偎巢,到底是詐尸還是另有隱情,我是刑警寧澤兼耀,帶...
    沈念sama閱讀 33,756評(píng)論 4 324
  • 正文 年R本政府宣布压昼,位于F島的核電站,受9級(jí)特大地震影響翠订,放射性物質(zhì)發(fā)生泄漏巢音。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評(píng)論 3 307
  • 文/蒙蒙 一尽超、第九天 我趴在偏房一處隱蔽的房頂上張望官撼。 院中可真熱鬧,春花似錦似谁、人聲如沸傲绣。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,315評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)秃诵。三九已至,卻和暖如春塞琼,著一層夾襖步出監(jiān)牢的瞬間菠净,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,539評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工彪杉, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留毅往,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,578評(píng)論 2 355
  • 正文 我出身青樓派近,卻偏偏與公主長(zhǎng)得像攀唯,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子渴丸,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容