Effective Java讀書筆記七:泛型(23-29 部分章節需要重讀)

衣舞晨風發表於2017-02-06

第23條:請不要在新程式碼中使用原生態型別

從java1.5發行版本開始,java就提供了一種安全的替代方法,稱作無限制的萬用字元型別,如果要使用範型,但是確定或者不關心實際的引數型別,就可以用一個問號代替。例如範型Set<?>的無限制萬用字元型別為Set<?>。這是最普通的引數化Set型別,可以持有任何集合。

關於Set<?>的兩個事實:
1、因為“?”標記可以代表任何型別,Set<?>可以持有任何型別的元素
2、由於不知道“?”代表的型別,所有我們不能把任何元素放到Set<?>集合中

由於可以將任何元素放進使用原生態型別的集合中,因此很容易破壞該集合的型別約束條件;但不能將任何元素(除了null之外)放到Collection<?>中。

“不要在新程式碼中使用原生態型別”,這條規則有兩個例外,這是因為“泛型資訊在執行時就會被擦除”。

在獲取類資訊中必須使用原生態型別(陣列型別和基本型別也算原生態型別),規範不允許使用引數化型別。換句話說:List.class,String[].class和int.class都是合法,但是List<String>.class和List<?>.class都是不合法的。

這條規則的第二個例外與instanceof操作符有關,由於泛型資訊在執行時已被擦除,因此在引數化型別而不是無限制萬用字元型別(如List<?>)上使用instanceof操作符是非法的,用無限制萬用字元型別代替原生態型別,對instanceof操作的行為不產生任何影響。在這種情況下,尖括號<>和問號?就顯得多餘了。

下面是利用泛型來使用instanceof操作符的首先方法:

if(o instanceof set){
       Set<?> m = (Set<?>)o;
       // ...
}

注意,一旦確定這個o是個Set,就必須將它轉換成通配型別Set<?>,則不是轉換成原生態型別Set,否則Set會引起編譯時警告。

總之,使用原生態型別會在執行時導致異常,因此不要在新程式碼中使用。原生態型別只為了與引入泛型之前的遺留程式碼進行相容和互用而提供的。另外Set<Object>是個引數化型別,表示可以包括任何物件型別的一個集合;Set<?>則是一個萬用字元型別,表示只能包含某種未知物件型別的一個集合;Set則是個原生態型別,它脫離了泛型系統。前兩者是安全的,最後一種不安全。

術語介紹:
原生態型別:List
引數化的型別:List<String>
泛型:List<E>
有限制型別引數:List<E extends Number>
形式型別引數:E
無限制萬用字元型別:List<?>
有限制萬用字元型別:List<? extends Number>
遞迴型別限制:List <T extends Comparable>
泛型方法: static<E> List<E> asList(E[] a)

第24條:消除非受檢警告

用泛型程式設計時,會遇到許多編譯器警告:非受檢強制轉換警告、非受檢方法呼叫警告、非受檢普通陣列建立警告,以及非受檢轉換警告。

要儘可能地消除每一個非受檢警告。如果消除了所有警告,就可以確保程式碼是型別安全的。

如果無法消除警告,同時可以證明引起警告的程式碼是型別安全的,只有在這種情況下才可以用一個@SuppressWarnings(“unchecked”)註解來禁止這條警告。

SuppressWarnings註解可以用在任何粒度的級別中,從單獨的區域性變數到整個類都可以。應該始終在儘可能小的範圍中使用SuppressWarnings註解。它通常是個變數宣告,或者是非常簡短的方法或者構造器。永遠不要在整個類上使用SuppressWarnings,這麼做可能會掩蓋了重要的警告。

總而言之,非受檢警告很重要,不要忽略它們。每一條警告都表示可能在執行時丟擲ClassCastException異常。要盡最大的努力消除這些警告。如果無法消掉同時確實是型別安全的,就可以在儘可能小的範圍中,用@SuppressWarnings(“unchecked”)註解來禁止這條警告。要用註釋把禁止該警告的原因記錄下來。

第25條:列表優先於陣列

1、陣列是協變的(convariant),如果Sub是Super的子型別,那麼陣列型別Sub[]就是Super[]的子型別。
泛型確實不可變的,List<Sub>不是List<Super>的子型別。

2、陣列是具體化的(reified),因此陣列在執行時才知道並檢查它們的元素型別約束。
泛型則是通過擦除(erasure)來實現,因此泛型只在編譯時強化它們的型別資訊,並在執行時丟棄(或者擦除)它們的元素型別約束。擦除就是使泛型可以與沒有使用泛型的程式碼隨意進行互用(見第23條)。
下面程式碼片段是合法的:

Object[] objectArray = new Long[1];
objectArray[0] ="I don't fit in";//Throws ArrayStoreExecption

但是下面的程式碼則是不合法的:

List<Object> ol = new ArrayList<Long>();//Incompatible types 不相容型別
ol.add("I don't fit in");

這其中無論是那種方法,都不能將String型別放進Long容器中,但是利用陣列,你會在執行時丟擲所犯的錯誤,利用List列表,則是在編譯時就能看到發生的錯誤,我們當然希望在編譯時發現錯誤了。

由於上述這些根本的區別,因此陣列和泛型不能很好的混合使用。例如,建立泛型,引數化型別或者型別引數的陣列是非法的。這些陣列建立表示式沒有一個是合法的:new List<E>、new List<String>[]和new E[]。這些在編譯的時都會導致一個generic array creation(泛型建立陣列)錯誤。

第26條:優先考慮泛型(需要重新讀)

使用泛型比使用需要在客戶端程式碼中進行轉換的型別來的更加安全,也更加容易。在設計新型別的時候,要確保它們不需要這種轉換就可以使用。這通常意味著要把類做成是泛型的。

第27條:優先考慮泛型方法(需要重新讀)

泛型方法就想泛型物件一樣,使用起來比要求客戶端轉換輸入引數並返回值的方法來得更加安全,也更加容易。就像型別一樣,你應該確保新方法可以不用轉換就能使用,這通常意味著要將它們泛型化。

第28條:利用有限制萬用字元來提升API的靈活性(需要重新讀)

為了獲得最大限度的靈活性,要在表示生產者和消費者的輸入引數上使用萬用字元型別。如果某個輸入引數即是生產者,又是消費都,那麼萬用字元型別對你就沒有什麼好處了,因為你需要嚴格的型別匹配,這是不用任何萬用字元而得到的。

下面的助記符便於讓你記住要使用哪種萬用字元型別:

PECS 表示:producer-extends, consumer-super
換句話說,如果引數化型別表示一個T生產者,就使用<? extends T>;如果它表示一個T消費者,就使用<? super T>。

PECS這個助記符突出了使用萬用字元的基本原則。Naftalin和Wadler稱之為Get and Put Principle

第29條:優先考慮型別安全的異構容器(需要重新讀)

集合API說明了泛型的一般用法,限制你每個容器只能有固定數目的型別引數。你可以通過將型別引數放在鍵上而不是容器上這一限制。對於這種型別安全的異構容器,可以用Class物件作為鍵。以這種方式使用的Class物件稱作型別令牌。

《Effective Java中文版 第2版》PDF版下載:
http://download.csdn.net/detail/xunzaosiyecao/9745699

作者:jiankunking 出處:http://blog.csdn.net/jiankunking

相關文章