Effective-java 3 中文翻譯系列 (Item 26 不要使用原始類型)

原文鏈接

文章也上傳到

github

(歡迎關(guān)注量没,歡迎大神提點芥备。)


ITEM26 不要使用原始類型

從Java5開始引入范型。
在沒有范型的時候蚀腿,如果有人不小心將錯誤的類型加入到collection中程癌,就會造成運行時的錯誤玻佩。
有了范型之后,你能告訴編譯器席楚,哪種類型被允許加入到collection 中咬崔,而且能在編譯期間就發(fā)現(xiàn)錯誤。這個系列的文章會告訴你如何更優(yōu)雅的使用范型烦秩。

首先垮斯,有幾個術(shù)語解釋一下。一個類或者接口如果聲明了一個或多個類型參數(shù)只祠,就可以被叫做范型類或范型接口兜蠕。例如,List接口有一個類型參數(shù):E抛寝,代表元素的類型熊杨,接口的全名是:List<E>.讀作E列表。但是大多數(shù)人簡單讀作列表盗舰。范型類和范型接口被統(tǒng)稱為范型類型晶府。
每個范型類型的組成是在類名或者接口名后面跟著一對尖括號,里面是真實參數(shù)類型對應(yīng)范型類型的列表钻趋。例如List<String>(讀作string列表)代表的是每一個元素都是String類型的list(String是對應(yīng)形參E的真實參數(shù)類型)川陆。
每一個范型都定義了一個原始類型,如List<E>中的List(它們的存在主要是為了兼容范型之前的代碼)蛮位。


// 郵票集合. 只能存儲郵票實例.
private final Collection stamps = ... ;

如果你這樣聲明而且錯誤的放了一個不是郵票的實例進入到這個集合中较沪,編譯和運行期間并不會報錯:

//錯誤的放一個coin類型實例進入郵票集合中
stamps.add(new Coin( ... )); // Emits "unchecked call" warning

直到你嘗試從集合中取出這個coin時才會報錯:

for (Iterator i = stamps.iterator(); i.hasNext();)
    Stamp stamp = (Stamp) i.next(); // 拋出異常ClassCastException
stamp.cancel();

但是我們的原則是越早發(fā)現(xiàn)問題越好,最好是在編譯期就能發(fā)現(xiàn)問題失仁。在上面這種情況下尸曼,在運行時也只能到執(zhí)行到上面代碼時才發(fā)生錯誤。而且編譯器不會告訴你是因為添加了coin進入stamps導致的萄焦,它僅能告訴你“Contains only Stamp instances.”控轿。

如果使用范型的話,可以指定類型:

private final Collection<Stamp> stamps = ... ;

編譯期就知道stamps只能包含stamp類型的實例并且保證這個規(guī)則是被滿足的。如果插入其他類型的實例(比如coin)解幽,編譯器會告訴你發(fā)生了錯誤:

Test.java:9: error: incompatible types: Coin cannot be converted
to Stamp
c.add(new Coin());
       ^

編譯器會隱含的添加強轉(zhuǎn)的邏輯,并保證它們不會失敗烘苹。把coin加入stamp集合的例子雖然看起來不太恰當躲株,但這也確實會發(fā)生:例如很容易會把BitInteger對象放到BitDecimal集合中。

即使使用原始類型是合法的镣衡,但是你也盡量不要這樣做霜定。因為你是使用原始類型會失去使用范型的安全性和表達的便利性。 既然你不應(yīng)該使用它們廊鸥,那么為什么語言還要設(shè)計允許你使用呢望浩?答案是為了兼容性。java出現(xiàn)范型的時候是它被發(fā)明出來的十年后惰说,當時已經(jīng)存在大量的代碼沒有使用范型磨德,所以老的代碼應(yīng)該是合法的,并且老代碼也應(yīng)該是可以和范型正常交互的,方法可以正常的傳遞真實類型參數(shù)吆视,反之亦然典挑。這被叫做遷移兼容性

即使你不使用像List一樣的具體真實類型啦吧,你也可以很方便的使用參數(shù)化類型來允許插入任何實例類型您觉,像List<Object>。那么原始類型List和List<Object>有什么不同呢授滓?簡單來說琳水,后者明確的告訴編譯器它能添加對象類型的參數(shù)。你能將List<String>傳遞到List般堆,但你不能將其傳遞到List<Object>在孝。范型是有子類型規(guī)則的,List<String>是List的子類型淮摔,但是不是List<Object>的子類(Item28)浑玛。總結(jié)噩咪,使用像List的原始類型顾彰,就會失去類型安全性。具體說明請看例子:

// Fails at runtime - unsafeAdd method uses a raw type
(List)!
public static void main(String[] args) {
    List<String> strings = new ArrayList<>();
    unsafeAdd(strings, Integer.valueOf(42));
    String s = strings.get(0); // Has compiler-generated cast
}

private static void unsafeAdd(List list, Object o) {
    list.add(o);
}

程序可以編譯胃碾,但是會收到警告:


Test.java:10: warning: [unchecked] unchecked call to add(E) as a
member of the raw type List
list.add(o);
       ^

實際上涨享,運行時當程序嘗試執(zhí)行strings.get(0)時,你會得到ClassCastException異常仆百。因為要將Integer轉(zhuǎn)換成String報錯厕隧。如果你把List替換成List<Object>然后重新編譯程序,你將會發(fā)現(xiàn)不能編譯通過而是發(fā)生錯誤。

Test.java:5: error: incompatible types: List<String> cannot be
converted to List<Object>
unsafeAdd(strings, Integer.valueOf(42));
    ^

你可能想使用原始類型作為集合吁讨,包含一些未知類型的對象髓迎。例如,假如你想寫一個方法返回兩個sets中所共有的元素個數(shù)建丧,代碼如下:


// Use of raw type for unknown element type - don't do
this!
static int numElementsInCommon(Set s1, Set s2) {
    int result = 0;
    for (Object o1 : s1)
        if (s2.contains(o1))
            result++;
        
    return result;
}   

這樣的方法可以正常工作排龄,但是是危險的。更安全的方法是使用無限制通配符類型翎朱。
如果當你想使用范型橄维,但是你又不知道或者不確定真實類型是什么,你可以使用一個問號標志代替拴曲。例如争舞,Set<E>的無限制通配符號類型是Set<?>,讀作某種類型的set。這是一種可用范圍更廣的Set類型澈灼,可以包含任何set竞川。將numElementsInCommon方法聲明稱無限制通配符類型是:

// Uses unbounded wildcard type - typesafe and flexible
static int numElementsInCommon(Set<?> s1, Set<?> s2) { ... }

Set<?>和Set類型的區(qū)別是什么呢?
通配符是更加安全的叁熔。因為你可以將任何原始類型元素放入到一個集合中流译,這樣會很容易的破壞集合類型的不變性(就像上面的unsafeAdd一樣)。但是你不能添加任何元素(除了null)外到Collection<?>者疤。嘗試這么做會在編譯期間報錯:

WildCard.java:13: error: incompatible types: String cannot be
converted to CAP#1
c.add("verboten");
    ^
where CAP#1 is a fresh type-variable:
CAP#1 extends Object from capture of ?

很明顯這個錯誤已經(jīng)提示了一些期望的信息福澡,而且編譯器也完成了它的使命,防止了你去破壞集合的不可變性驹马。不但防止了你放入一個除了null之外的元素進入Collection<?>,而且不能確定你從集合中取出的是何種類型革砸。如果這些限制對你來說不可接受,那么你就可以使用范型方法(Item30)或者無限制的通配符號類型(Item31)糯累。

但是對于這條限制也有一些特殊的例外情況你必須使用原始類型算利。

  • 字面值類型
  • 使用instanceof
if (o instanceof Set) { // Raw type
    Set<?> s = (Set<?>) o; // Wildcard type
    ...
}

快速回顧一下:

  • Set<Object>代表可以存放任何對象類型的set;
  • Set<?>表示可以存放任何未知對象類型的set泳姐;
  • Set是原始類型效拭,根據(jù)范型系統(tǒng)輸出。
    前兩個是安全的胖秒,最后一個不是缎患。

最后介紹下術(shù)語:

術(shù)語 例子
參數(shù)化類型 List<String)
真實類型 String
范型 List<E>
正式類型參數(shù) E
無限制通配符類型 List<?>
原始類型 List
有限制類型參數(shù) <E extends Number>
遞歸類型邊界 <T extends Comparable<T>>
有限制通配符類型 List<? extends Number>
范型方法 static <E> List<E> asList(E[] a)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市阎肝,隨后出現(xiàn)的幾起案子挤渔,更是在濱河造成了極大的恐慌,老刑警劉巖风题,帶你破解...
    沈念sama閱讀 206,602評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件判导,死亡現(xiàn)場離奇詭異嫉父,居然都是意外死亡,警方通過查閱死者的電腦和手機眼刃,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評論 2 382
  • 文/潘曉璐 我一進店門绕辖,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人擂红,你說我怎么就攤上這事仪际。” “怎么了篮条?”我有些...
    開封第一講書人閱讀 152,878評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長吩抓。 經(jīng)常有香客問我涉茧,道長,這世上最難降的妖魔是什么疹娶? 我笑而不...
    開封第一講書人閱讀 55,306評論 1 279
  • 正文 為了忘掉前任伴栓,我火速辦了婚禮,結(jié)果婚禮上雨饺,老公的妹妹穿的比我還像新娘钳垮。我一直安慰自己,他們只是感情好额港,可當我...
    茶點故事閱讀 64,330評論 5 373
  • 文/花漫 我一把揭開白布饺窿。 她就那樣靜靜地躺著,像睡著了一般移斩。 火紅的嫁衣襯著肌膚如雪肚医。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,071評論 1 285
  • 那天向瓷,我揣著相機與錄音肠套,去河邊找鬼。 笑死猖任,一個胖子當著我的面吹牛你稚,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播朱躺,決...
    沈念sama閱讀 38,382評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼刁赖,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了长搀?” 一聲冷哼從身側(cè)響起乾闰,我...
    開封第一講書人閱讀 37,006評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎盈滴,沒想到半個月后涯肩,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體轿钠,經(jīng)...
    沈念sama閱讀 43,512評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,965評論 2 325
  • 正文 我和宋清朗相戀三年病苗,在試婚紗的時候發(fā)現(xiàn)自己被綠了疗垛。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,094評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡硫朦,死狀恐怖贷腕,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情咬展,我是刑警寧澤泽裳,帶...
    沈念sama閱讀 33,732評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站破婆,受9級特大地震影響涮总,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜祷舀,卻給世界環(huán)境...
    茶點故事閱讀 39,283評論 3 307
  • 文/蒙蒙 一瀑梗、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧裳扯,春花似錦抛丽、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至冤吨,卻和暖如春狡门,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背锅很。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評論 1 262
  • 我被黑心中介騙來泰國打工其馏, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人爆安。 一個月前我還...
    沈念sama閱讀 45,536評論 2 354
  • 正文 我出身青樓叛复,卻偏偏與公主長得像,于是被迫代替她去往敵國和親扔仓。 傳聞我的和親對象是個殘疾皇子褐奥,可洞房花燭夜當晚...
    茶點故事閱讀 42,828評論 2 345

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