《Effective Java》读书笔记 - 5.泛型
Chapter 5 Generics
Item 23: Don’t use raw types in new code
雖然你可以把一個List<String>傳給一個List類型(raw type)的參數(shù),但你不應(yīng)該這么做(因為允許這樣只是為了兼容遺留代碼),舉個例子:
// Uses raw type (List) - fails at runtime! public static void main(String[] args) {List<String> strings = new ArrayList<String>();unsafeAdd(strings, new Integer(42));String s = strings.get(0); // Compiler會自動加上cast } private static void unsafeAdd(List list, Object o) {list.add(o); }以上代碼說明如果你把List<String>類型轉(zhuǎn)換成List類型,就可以往里面加任何東西了,編譯器不會做檢查,就很危險。但是你也不能把List<String>轉(zhuǎn)換成List<Object>,但是你可以把List<String>轉(zhuǎn)換成List<?>,讀作List of some type(某種類型的List),但這時候你就不能忘里面add東西了(除了null),因為這個List包含某種類型,但你又不知道(或不關(guān)心)是具體是什么類型,那你加進去的東西很可能跟它應(yīng)有的類型不匹配,所以你只能拿出來一個東西(并定義成Object類型)。如果你實在想add,可以用generic methods或bounded wildcard types。
由于在運行時“都變成了raw type”,所以instanceof后面只能用raw type:
// Legitimate use of raw type - instanceof operator if (o instanceof Set) { // Raw typeSet<?> m = (Set<?>) o; // Wildcard type }這里的Cast不會造成編譯器warning。
Item 24: Eliminate unchecked warnings
當寫Generic的時候,通常會遇到很多warning。而你應(yīng)該盡量eliminate掉每一個warning,這樣到了runtime你的代碼才更可能不會拋出ClassCastException。如果你沒法消除這個warning,但你能證明是typesafe的,那你應(yīng)該用@SuppressWarnings("unchecked")。SuppressWarnings可以用在類上,方法上,變量聲明上,你應(yīng)該將其用在the smallest scope possible,因為如果你比如用在整個class上,那你可能mask了一些關(guān)鍵性的warning,所以千萬別這么做。有時候為了這個原則,你還不得不把某句語句拆成兩句寫,比如:
return (T[]) Arrays.copyOf(elements, size, a.getClass());由于不能在return語句上加SuppressWarnings,所以你只能拆成兩句:
@SuppressWarnings("unchecked") T[] result = (T[]) Arrays.copyOf(elements, size, a.getClass()); return result;每次你用@SuppressWarnings("unchecked")時,都應(yīng)該注釋一下你為什么要這么做。
Item 25: Prefer lists to arrays
數(shù)組是covariant的,意思就是如果Sub是Super的子類,那么Sub[]就是Super[]的“子類型”,這也就意味著你可以把String[]轉(zhuǎn)成Object[],然后加一個Object對象進去,然后到runtime會報錯。而泛型是invariant的,也就是對于任何的x和y,List<x>和List<y>沒有任何關(guān)系。
 你無法new一個跟泛型有關(guān)的數(shù)組,比如以下都是錯誤的:new List<E>[], new List<String>[], new E[]。為什么?書上舉了個例子我懶得寫了,反正我個人總結(jié)下來就是:都怪擦除,因為T[]到運行時其實就相當于Object[],你可以往里面放任何東西,但按理說你應(yīng)該只能放是T的東西進去。所以說不要把varargs和泛型混用,因為varargs其實就相當于創(chuàng)建了一個數(shù)組當成參數(shù)。由于這種無法創(chuàng)建數(shù)組的限制以及數(shù)組的協(xié)變性,你可以考慮用List<T>代替T[],比如可以用new ArrayList<E>(list)代替(E[]) list.toArray(),會安全得多。
 總結(jié)來說,泛型提供了編譯時但非運行時的type safety,而數(shù)組恰好相反。
Item 26: Favor generic types
generic types就是Generic classes and interfaces。這個item就是教你怎么把你的類變成泛型類,比如我們要把item 6中基于Object[]的Stack升級為泛型的,那我們就把所有“Object”替換成“E”,并在類聲明中加上泛型參數(shù)。這樣會有一個問題就是:elements = new E[DEFAULT_INITIAL_CAPACITY]這句話不能通過編譯。因為你不能創(chuàng)建泛型數(shù)組,解決方法是:
 1.elements = (E[]) new Object[DEFAULT_INITIAL_CAPACITY];
 因為elements是個private的field,不會泄露給client,而唯一給這個數(shù)組“裝元素”的地方就是push方法,而push方法push進去的東西保證是E類型的(通過編譯器),所以你可以安心地加上@SuppressWarnings("unchecked")。(給這句所在constructor加,因為這句是賦值不是聲明,所以加不了)
 2.先elements = new Object[DEFAULT_INITIAL_CAPACITY]; 然后把elements的定義改成Object[]類型的,最后E result = (E) elements[--size];就行了。
 同理,因為push的時候已經(jīng)確保元素肯定是E,所以這里的warning也可以suppress掉。這兩種方法都可以,基本只是個人喜好問題。
Item 27: Favor generic methods
“Static utility methods”通常是泛型化的good candidates。在調(diào)用泛型方法的時候不需要顯式指定泛型參數(shù)的具體類型,編譯器會自己推斷出來,這叫type inference。
 后面這個generic singleton factory我來回看了幾遍終于有那么一點似乎看懂了,首先例子代碼如下:
一開始我在想new UnaryFunction<Object>(){...}這句話是什么意思,為什么這里是Object而不能是T,后來一想:匿名類是同時聲明和創(chuàng)建的,而創(chuàng)建一個泛型類的實例必須指定具體的type parameter,所以這里就相當于聲明了一個 實現(xiàn)了UnaryFunction<T>的類,然后創(chuàng)建了一個它的實例(泛型參數(shù)是Object)。然后identityFunction方法是一個泛型的static factory method,會把UnaryFunction<Object>類型轉(zhuǎn)換成 “調(diào)用這個泛型方法的時候被推斷出來的類型 的類型的UnaryFunction”。先看一下用法:
public static void main(String[] args) {String[] strings = { "jute", "hemp", "nylon" };UnaryFunction<String> sameString = identityFunction();for (String s : strings)System.out.println(sameString.apply(s));Number[] numbers = { 1, 2.0, 3L };UnaryFunction<Number> sameNumber = identityFunction();for (Number n : numbers)System.out.println(sameNumber.apply(n)); }第一次調(diào)用identityFunction()的時候被推斷出來的類型是String,第二次是Number。然后以第一次為例,在調(diào)用sameString.apply(s)的時候,相當于編譯器就會調(diào)用UnaryFunction<String>接口中的public String apply(String arg)方法,所以編譯器此時會檢查s這玩意兒是不是Sting,發(fā)現(xiàn)沒問題,OK,返回的結(jié)果也會被編譯器cast成String,而這里你的實際方法(return arg;)啥都沒做,所cast肯定不會報錯。這個例子的意思關(guān)鍵在于static factory method里面的那個cast: (UnaryFunction<T>) IDENTITY_FUNCTION,正是因為這個cast,所以client代碼才能讓 任何類型的UnaryFunction 都共享同一個實例(IDENTITY_FUNCTION )。
 但是我在普通的client代碼里面試了一下,無法將List<Object> cast成 List<String>,看來這個技巧也只能在泛型方法里面用了。C#的類似實現(xiàn)(雖然可能不是單例):
聽起來很玄乎的一個概念recursive type bound,比如:<T extends Comparable<T>> may be read as “for every type T that can be compared to itself,”。
 總之,generic methods和generic types都不需要client進行各種cast。
Item 28: Use bounded wildcards to increase API flexibility
為了更好的靈活性,應(yīng)該在輸入?yún)?shù)上用wildcard types。如果這個輸入?yún)?shù)代表了一個producer就用entends,如果代表了一個consumer就用super,比如如果一個方法的參數(shù)聲明是Collection<E> container,如果在方法內(nèi)部只會從container讀取E的instance(也就是E只能作為container方法中的返回類型),也就是container只是作為生產(chǎn)者,那么就應(yīng)該把聲明改成Collection<? extends E> container。(你可以這么記,不管返回什么,反正放到E類型的變量里肯定是安全的)同理,如果在方法內(nèi)部,比如會把E的instance加到container里去,那么container就是消費者(也就是E會作為輸入類型),那么就應(yīng)該聲明成Collection<? super E> container。如果既是生產(chǎn)者又是消費者,那就不能用bounded wildcards了。
 注意不要在返回類型上用wildcard types,因為如果你這么做了會迫使client code里面也要受到wildcard types的限制,比如你返回了一個Set<E extends E>,那么得到這個東西的client就只能在它上面進行g(shù)et操作,而不能add了。正確的用法是,你應(yīng)該讓client根本不用去思考wildcard types,wildcard types只是讓你的API能接受更多的類型。
 因為type inference的規(guī)則很復(fù)雜,有時候type inference會推理錯,這時候你需要顯示地指定一個type parameter,比如:Union.<Number>union(integers, doubles)。
 item 27中有這么一個方法:
現(xiàn)在我們把它增強成用wildcard type,變成這樣:
public static <T extends Comparable<? super T>> T max(List<? extends T> list)首先把list的類型改成“生產(chǎn)者”很好理解,因為list只返回一個Iterator<E>,而這個Iterator<E>的next方法的聲明是“E next()”,E是返回類型。但為什么這里要把Comparable<T>改成Comparable<? super T>。首先看一下Comparable<T>的定義:
public interface Comparable<T>{int compareTo(T o) }看到了嗎,T是輸入?yún)?shù),所以Comparable<T>的“實例”是個消費者。 比如如果你這么調(diào)用上面的那個方法:
List<Apple> apples = new...; Apple maxApple = max(apples);那么這里的Apple類并不一定要實現(xiàn)Comparable<Apple>,也可以只實現(xiàn)Comparable<Fruit>,當然Fruit是Apple的基類。假設(shè)Apple只知道怎么和Fruit比,然后當運行到方法體內(nèi)“t.compareTo(result)”這句的時候,t是一個Apple,result也是一個Apple,但是t只知道怎么和另一個Fruit比,但是result是一個Apple當然也是一個Fruit,所以沒問題。其實上面解釋地這么麻煩,不如你只要記住“always use Comparable<? super T> in preference to Comparable<T>”就行了(Comparator也一樣)。
 最后記得還要把方法體中的Iterator<T>改成Iterator<? extends T>。
下面是兩種等價的方法聲明:
// Two possible declarations for the swap method public static <E> void swap(List<E> list, int i, int j); public static void swap(List<?> list, int i, int j);作者說第二種更好,因為更簡單,且不需要考慮type parameter。如果一個type parameter在方法聲明中只出現(xiàn)了一次,那么就應(yīng)該把它替換成unbounded wildcard或bounded wildcard。為什么必須是“只出現(xiàn)了一次”?書上沒說但我個人理解是因為如果出現(xiàn)了兩次:public static <E> void swap(List<E> list1,List<E> list2),那么這里的list1包含的元素和list2包含的元素應(yīng)該是相同的類型,如果你全都換成“?”,那么list1和list2完全可以包含不同的類型。
 但是問題又來了,如果你單純這么實現(xiàn):
會發(fā)現(xiàn)不行,因為不能放東西到List<?>里去,解決辦法是依靠“type capture”,寫個helper方法:
public static void swap(List<?> list, int i, int j) {swapHelper(list, i, j); } // Private helper method for wildcard capture private static <E> void swapHelper(List<E> list, int i, int j) {list.set(i, list.set(j, list.get(i))); }雖然書上的解釋有點莫名其妙,但我選擇“信了”,感覺記住就行,只是個小技巧。我覺得可以這么理解:因為一個泛型方法被調(diào)用的時候肯定要指定泛型參數(shù)具體是什么,如果你不指定那就只能靠編譯器推斷,而在這里編譯器就會“capture”到?代表的東西。
Item 29: Consider typesafe heterogeneous containers
這一小節(jié)就是告訴你怎么實現(xiàn)這么一個類,保存 你最喜歡的 某個類型的一個實例:
public class Favorites {private Map<Class<?>, Object> favorites = new HashMap<Class<?>, Object>();public <T> void putFavorite(Class<T> type, T instance) {if (type == null) throw new NullPointerException("Type is null");favorites.put(type, type.cast(instance));}public <T> T getFavorite(Class<T> type) {return type.cast(favorites.get(type));} }你可以用putFavorite來存一個“T類型的實例”,然后通過getFavorite來獲取這個實例。
 所以說這里的T和T類型的value是一一對應(yīng)的,你可以放很多種不同的類型進去。你可以這樣使用:
其實這里用Class<T>來作為“Key”是因為自JDK1.5來Class類就被泛型化了,比如String.class是Class<String>的類型、Integer.class是Class<Integer>的類型,當把這樣一個“class literal”(應(yīng)該就是指“String.class”這種寫法)傳給某個方法的時候,通常把它叫做type token。而你完全可以自己寫一個類,比如Holder<T>來作為“Key”,但是不如Class好,因為Class有一些方法比如cast可以不用讓你suppress warning(我個人認為的)。上面的type.cast方法其實就是Java’s cast operator對應(yīng)的“動態(tài)版本”,它只是檢查一下它的參數(shù)是不是被自己代表的類型,不是的話就拋出ClassCastException:
public class Class<T> {T cast(Object obj); }另外,說些沒什么關(guān)聯(lián)的事兒:如果你把Class<?>類型轉(zhuǎn)換成,比如Class<? extends Apple>,會得到warning,那么你可以用asSubclass這個方法,比如假設(shè)你得到了一個Class<?>類型的變量apple,然后你可以apple.asSubclass(Apple.class),意思就是“把Class<?>變成Class<Apple>”(反正你就這么理解吧),如果這個apple指向的對象并不是一個“Apple對象”的Class object,那就拋出異常。
轉(zhuǎn)載于:https://www.cnblogs.com/raytheweak/p/7190157.html
總結(jié)
以上是生活随笔為你收集整理的《Effective Java》读书笔记 - 5.泛型的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: 幼儿园音乐教案《摇篮曲》教学设计与反思
- 下一篇: 事业单位工作员年度考核方案3篇
