一文讀懂《Effective Java》第23條:不要在新代碼中使用原生態類型


點擊上方藍字關注咱們javascript





Java1.5 發行版增長了泛型(Generic)。
泛型出現前,集合讀取的每一個對象都必須進行轉換,若是不當心插入類型錯誤對的對象,運行時的轉換處理會報錯。
泛型出現後,咱們經過泛型能夠告訴編譯器每一個集合能夠接受哪些對象類型,讓編譯器自動爲集合的元素插入進行轉化,而且在編譯時告知咱們是否插入了類型錯誤的對象。



一些泛型的專業術語




泛型類或泛型接口:聲明中具備一個或多個類型參數(type parameter)的類或者接口,統稱爲泛型。eg,jdk1.5以後,List 接口只有單個類型參數E,表示列表的元素類型,因此他的接口名稱應該是List<E>,可是人們經常把它簡稱爲List。css

參數化的類型(parameterized type),構成格式是:類或接口的名稱 + 尖括號(<>)將泛型形式參數的實際類型參數列表括起來。
html

每一個泛型都定義類一個 原生態類型(raw type),即不帶任何實際類型參數的泛型名稱。eg,List<E> 對應的原生態類型是List。原生態類型就至關於從類型聲明中刪除了泛型信息。java




泛型:編譯期及早發現錯誤




使用泛型進行編碼,有兩個好處:nginx

  • 優勢1:讓編寫代碼時在編譯期及早發現錯誤,而且助於定位報錯位置
  • 優勢2:集合使用泛型,從集合中遍歷元素時不須要再進行手工轉換了 (編譯器替咱們完成隱式轉換,並確保過程不會失敗,不管咱們使用的是否for-each循環)


下面咱們經過一個例子闡述清楚,代碼以下:程序員

 /** * @exception ClassCastException */ private static void testGenericeBeforejdk5() { Collection stamps = new ArrayList(); stamps.add(new Coin()); for (Iterator i = stamps.iterator(); i.hasNext();){ Stamp next = (Stamp)i.next(); //ClassCastException } }  private static void testGenericeAfterjdk5() { Collection<Stamp> stamps = new ArrayList(); stamps.add(new Coin());//編譯器告訴咱們錯誤  }   // 兩個測試內部類 static class Stamp{} static class Coin{}
  • testGenericeBeforejdk5()方法裏,咱們但願stamps集合只會存放Stamp 類元素,可是編碼時仍是不當心把一個coin放進了這個集合。那麼程序是不會在編譯時告訴程序員這個問題的,而是等到代碼真正執行時,出現了異常。
Exception in thread "main" java.lang.ClassCastException: effectivejava.no23.TestGeneric$Coin cannot be cast to effectivejava.no23.TestGeneric$Stamp at effectivejava.no23.TestGeneric.testGenericeBeforejdk5(TestGeneric.java:26) at effectivejava.no23.TestGeneric.main(TestGeneric.java:14)

  • testGenericeAfterjdk5()方法裏,咱們使用了泛型定義了集合的參數類型。經過這條聲明,編譯器知道 stamps 集合應該只包含Stamp 實例,並給以保證。所以在代碼開發時,咱們不當心將一個coin 實例放進stamps集合時,編譯器會及時提醒咱們併產生一條編譯錯誤信息,準確告知程序員哪裏出現錯誤。web

Error:(20, 28) java: 不兼容的類型: effectivejava.no23.TestGeneric.Coin沒法轉換爲effectivejava.no23.TestGeneric.Stamp
  • 經過比較,咱們還能發現,集合使用泛型,從集合中遍歷元素時不須要再進行手工轉換了。typescript




原生類型與泛型類型的區別






其1、使用原生態類型,會失掉泛型在安全性和其餘表述性方面的優點。express

  • 爲何繼續容許使用原生態類型呢?Java 平臺發展至今,已經存在大量的沒有使用泛型的Java 代碼了,人們認爲讓全部這些代碼保持合法,且可以與泛型的代碼互用,爲了這個「移植兼容性」(Migration Compatibility)需求,促成了支持原生態類型的決定。

其2、原生態類型List 和 參數化類型List<Object>有區別。json

  • 原生態類型List,逃避了泛型檢查,List<Object>則明確告知編譯器:它可以持有任意類型的對象。eg,List<String>能夠傳遞給List,但不能傳遞給 List<Object>。
  • 泛型有子類型化(subtyping)的規則。List<String> 是原生態類型List 的一個子類型,但不是List<Object> 的子類型。


下面經過一個例子解讀二者的區別:

 private static void testSubTyping() { List<String> strings = new ArrayList<String>();    unsafeAdd(strings, new Integer(110)); String s = strings.get(0); // exception while run the method;  }   /** * 方法使用了原生態類型,因此能夠編譯。 */ private static void unsafeAdd(List list, Object o) { list.add(0); }    /**   * 方法使用了List<Object>替代原生態類型,因此編譯不會經過。   */ private static void unsafeAddV2(List<Object> list, Object o) { list.add(0); }


結論:使用List 這樣的原生態類型會丟掉類型安全性,可是使用List<Object> 這樣的參數化類型則不會。





泛型的不推薦使用場景




不要在新代碼中使用原生態類型,這條規則有兩個小小的例外,緣由是:泛型信息能夠在運行時被編譯器擦除了

  • 在類文字(class literal)中必須使用原生態類型,規範不容許使用參數化類型(但容許數組類型和基本類型)[JLS,15.8.2]

ClassLiteral: 

    TypeName {[ ]} . class 

    NumericType {[ ]} . class 

    boolean {[ ]} . class 

    void . class


class literal is an expression consisting of the name of a class, interface, array, or primitive type, or the pseudo-type void, followed by a '.' and the token class.

https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.8.2


  • instanceof 操做符對「無限制通配符」的參數化類型是無效非法的。,因爲泛型信息在運行中被擦除了,這種狀況下,尖括號(<>)和問號(?)顯得多餘了。
 private static void testInstanceOfInvalidOnGeneric(Object o) { if (o instanceof Set){ //一旦肯定o是個Set,必須轉換爲通配符類型Set<?>,而不是原生態類型Set。這樣能避免後續代碼出現運行時異常。 Set<?> set = (Set<?>) o; } }



總結



涉及到的術語表
術語 示例 所在條目
參數化的類型 List<String> 23
實際類型參數 String 23
泛型
List<E>
23
形式類型參數
E
23

無限制通配符類型參數
List<?>
23
原生態類型參數
List
23
有限制類型參數
List<E extends Number>
26
遞歸類型限制
List<T extends Comparable<T>>
27
有限制通配符類型參數
List<? extends Number>
28
泛型方法
static <E> List<E> asList(E[] a)
27
類型令牌
String.class
29




END





掃描二維碼

獲取技術乾貨

後臺技術匯




點個「在看」表示朕

已閱



本文分享自微信公衆號 - 後臺技術匯(gh_bbd0c11cb61f)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索