原文鏈接
文章也上傳到
(歡迎關(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) |