先看一個栗子,看看能不能找出來裡面的錯誤:
/** * 請不要在新代碼中使用原生態類型 * @author weishiyao * */ public class Test { public static void main(String[] args) { List<String> strings = new ArrayList<>(); unsafeAdd(strings, new Integer(42)); String string = strings.get(0); System.out.println(string); } private static void unsafeAdd(List list, Object object) { list.add(object); } }
運行一下,看一下報錯結果:
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String at com.weishiyao.effectiveJava.entry23.Test.main(Test.java:16)
對的,是一個ClassCastException,這是一個編譯器聲稱的轉換,因此一般會保證成功,但是我嘛在這個栗子中忽略了一條編譯器的警告,就會為此而付出代價。
但是,如果用unsafeAdd(List<Object> list, Object object)代替原生的List類型,我們的IDE會直接報錯。
在不確定或者不在乎集合中元素類型的情況下,你也許會使用原生態的類型。例如,假設想要編寫一個方法,它有兩個集合(set),並從中返回他們公有的元素的數量。如果不熟悉范型的話,可以參考以下方式來編寫這種方法:
static int numElementsInCommen(Set s1, Set s2) { int result = 0; for (Object object : s1) { if (s2.contains(object)) { result++; } } return result; }
這個方法倒是可行,但是它使用了原生態類型,這是很危險的。從java1.5發行版本開始,java就提供了一種安全的替代方法,稱作無限制的通配符類型,如果要使用范型,但是確定或者不關心實際的參數類型,就可以用一個問號代替。例如范型Set<E>的無限制通配符類型為Set<?>。這是最普通的參數化Set類型,可以持有任何集合。
在無限制通配類型Set<?>和原生態類型Set之間有什麼區別呢?這個問號真正起到作用了麼?這一點不需要贅述,但通配符類型是安全的,原生態類型則不安全。可以將任何元素放入使用原生態類型的集合中,因此很容易破壞該集合類型的約束條件;但不能將任何元素(除了null)放到Collection<?>中。
不要在新代碼中使用原生態類型,這條規則有兩個小小的例外,兩者都源於“范型信息可以在運行時被擦除”這一事實。在類文字中必須使用原生形態。規范不允許使用參數化類型。換句話說,List.class,String[].class和int.class都是合法的。但是List<String>.class和List<?>則不合法。
這條規則第二個例外與instanceof操作符有關。由於范型信息可以在運行時被擦除,一次在參數化類型而非無限制通配符上使用instanceof操作符時非法的。用無限制通配符類型代替原生態類型,對instanceof操作符的行為不回產生任何影響。在這種情況下,尖括號<>和問號?就顯得多余了。下面是利用范型來使用instanceof操作符的首選方法:
if (object instanceof Set) { // Raw type Set<?> m = (Set<?>) object; // Wildcard type }
注意,一旦確定這個object是個Set,就必須將他轉換成通配符類型Set<?>,而不是原生類型,這個是受檢的,否則的話我們的IDE還是會報錯的。