《Effective Java》读书笔记 - 5.泛型
Chapter 5 Generics
Item 23: Don’t use raw types in new code
雖然你可以把一個List<String>傳給一個List類型(raw type)的參數,但你不應該這么做(因為允許這樣只是為了兼容遺留代碼),舉個例子:
// 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>類型轉換成List類型,就可以往里面加任何東西了,編譯器不會做檢查,就很危險。但是你也不能把List<String>轉換成List<Object>,但是你可以把List<String>轉換成List<?>,讀作List of some type(某種類型的List),但這時候你就不能忘里面add東西了(除了null),因為這個List包含某種類型,但你又不知道(或不關心)是具體是什么類型,那你加進去的東西很可能跟它應有的類型不匹配,所以你只能拿出來一個東西(并定義成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。而你應該盡量eliminate掉每一個warning,這樣到了runtime你的代碼才更可能不會拋出ClassCastException。如果你沒法消除這個warning,但你能證明是typesafe的,那你應該用@SuppressWarnings("unchecked")。SuppressWarnings可以用在類上,方法上,變量聲明上,你應該將其用在the smallest scope possible,因為如果你比如用在整個class上,那你可能mask了一些關鍵性的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")時,都應該注釋一下你為什么要這么做。
Item 25: Prefer lists to arrays
數組是covariant的,意思就是如果Sub是Super的子類,那么Sub[]就是Super[]的“子類型”,這也就意味著你可以把String[]轉成Object[],然后加一個Object對象進去,然后到runtime會報錯。而泛型是invariant的,也就是對于任何的x和y,List<x>和List<y>沒有任何關系。
你無法new一個跟泛型有關的數組,比如以下都是錯誤的:new List<E>[], new List<String>[], new E[]。為什么?書上舉了個例子我懶得寫了,反正我個人總結下來就是:都怪擦除,因為T[]到運行時其實就相當于Object[],你可以往里面放任何東西,但按理說你應該只能放是T的東西進去。所以說不要把varargs和泛型混用,因為varargs其實就相當于創建了一個數組當成參數。由于這種無法創建數組的限制以及數組的協變性,你可以考慮用List<T>代替T[],比如可以用new ArrayList<E>(list)代替(E[]) list.toArray(),會安全得多。
總結來說,泛型提供了編譯時但非運行時的type safety,而數組恰好相反。
Item 26: Favor generic types
generic types就是Generic classes and interfaces。這個item就是教你怎么把你的類變成泛型類,比如我們要把item 6中基于Object[]的Stack升級為泛型的,那我們就把所有“Object”替換成“E”,并在類聲明中加上泛型參數。這樣會有一個問題就是:elements = new E[DEFAULT_INITIAL_CAPACITY]這句話不能通過編譯。因為你不能創建泛型數組,解決方法是:
1.elements = (E[]) new Object[DEFAULT_INITIAL_CAPACITY];
因為elements是個private的field,不會泄露給client,而唯一給這個數組“裝元素”的地方就是push方法,而push方法push進去的東西保證是E類型的(通過編譯器),所以你可以安心地加上@SuppressWarnings("unchecked")。(給這句所在constructor加,因為這句是賦值不是聲明,所以加不了)
2.先elements = new Object[DEFAULT_INITIAL_CAPACITY]; 然后把elements的定義改成Object[]類型的,最后E result = (E) elements[--size];就行了。
同理,因為push的時候已經確保元素肯定是E,所以這里的warning也可以suppress掉。這兩種方法都可以,基本只是個人喜好問題。
Item 27: Favor generic methods
“Static utility methods”通常是泛型化的good candidates。在調用泛型方法的時候不需要顯式指定泛型參數的具體類型,編譯器會自己推斷出來,這叫type inference。
后面這個generic singleton factory我來回看了幾遍終于有那么一點似乎看懂了,首先例子代碼如下:
一開始我在想new UnaryFunction<Object>(){...}這句話是什么意思,為什么這里是Object而不能是T,后來一想:匿名類是同時聲明和創建的,而創建一個泛型類的實例必須指定具體的type parameter,所以這里就相當于聲明了一個 實現了UnaryFunction<T>的類,然后創建了一個它的實例(泛型參數是Object)。然后identityFunction方法是一個泛型的static factory method,會把UnaryFunction<Object>類型轉換成 “調用這個泛型方法的時候被推斷出來的類型 的類型的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)); }第一次調用identityFunction()的時候被推斷出來的類型是String,第二次是Number。然后以第一次為例,在調用sameString.apply(s)的時候,相當于編譯器就會調用UnaryFunction<String>接口中的public String apply(String arg)方法,所以編譯器此時會檢查s這玩意兒是不是Sting,發現沒問題,OK,返回的結果也會被編譯器cast成String,而這里你的實際方法(return arg;)啥都沒做,所cast肯定不會報錯。這個例子的意思關鍵在于static factory method里面的那個cast: (UnaryFunction<T>) IDENTITY_FUNCTION,正是因為這個cast,所以client代碼才能讓 任何類型的UnaryFunction 都共享同一個實例(IDENTITY_FUNCTION )。
但是我在普通的client代碼里面試了一下,無法將List<Object> cast成 List<String>,看來這個技巧也只能在泛型方法里面用了。C#的類似實現(雖然可能不是單例):
聽起來很玄乎的一個概念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
為了更好的靈活性,應該在輸入參數上用wildcard types。如果這個輸入參數代表了一個producer就用entends,如果代表了一個consumer就用super,比如如果一個方法的參數聲明是Collection<E> container,如果在方法內部只會從container讀取E的instance(也就是E只能作為container方法中的返回類型),也就是container只是作為生產者,那么就應該把聲明改成Collection<? extends E> container。(你可以這么記,不管返回什么,反正放到E類型的變量里肯定是安全的)同理,如果在方法內部,比如會把E的instance加到container里去,那么container就是消費者(也就是E會作為輸入類型),那么就應該聲明成Collection<? super E> container。如果既是生產者又是消費者,那就不能用bounded wildcards了。
注意不要在返回類型上用wildcard types,因為如果你這么做了會迫使client code里面也要受到wildcard types的限制,比如你返回了一個Set<E extends E>,那么得到這個東西的client就只能在它上面進行get操作,而不能add了。正確的用法是,你應該讓client根本不用去思考wildcard types,wildcard types只是讓你的API能接受更多的類型。
因為type inference的規則很復雜,有時候type inference會推理錯,這時候你需要顯示地指定一個type parameter,比如:Union.<Number>union(integers, doubles)。
item 27中有這么一個方法:
現在我們把它增強成用wildcard type,變成這樣:
public static <T extends Comparable<? super T>> T max(List<? extends T> list)首先把list的類型改成“生產者”很好理解,因為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是輸入參數,所以Comparable<T>的“實例”是個消費者。 比如如果你這么調用上面的那個方法:
List<Apple> apples = new...; Apple maxApple = max(apples);那么這里的Apple類并不一定要實現Comparable<Apple>,也可以只實現Comparable<Fruit>,當然Fruit是Apple的基類。假設Apple只知道怎么和Fruit比,然后當運行到方法體內“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在方法聲明中只出現了一次,那么就應該把它替換成unbounded wildcard或bounded wildcard。為什么必須是“只出現了一次”?書上沒說但我個人理解是因為如果出現了兩次:public static <E> void swap(List<E> list1,List<E> list2),那么這里的list1包含的元素和list2包含的元素應該是相同的類型,如果你全都換成“?”,那么list1和list2完全可以包含不同的類型。
但是問題又來了,如果你單純這么實現:
會發現不行,因為不能放東西到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))); }雖然書上的解釋有點莫名其妙,但我選擇“信了”,感覺記住就行,只是個小技巧。我覺得可以這么理解:因為一個泛型方法被調用的時候肯定要指定泛型參數具體是什么,如果你不指定那就只能靠編譯器推斷,而在這里編譯器就會“capture”到?代表的東西。
Item 29: Consider typesafe heterogeneous containers
這一小節就是告訴你怎么實現這么一個類,保存 你最喜歡的 某個類型的一個實例:
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是一一對應的,你可以放很多種不同的類型進去。你可以這樣使用:
其實這里用Class<T>來作為“Key”是因為自JDK1.5來Class類就被泛型化了,比如String.class是Class<String>的類型、Integer.class是Class<Integer>的類型,當把這樣一個“class literal”(應該就是指“String.class”這種寫法)傳給某個方法的時候,通常把它叫做type token。而你完全可以自己寫一個類,比如Holder<T>來作為“Key”,但是不如Class好,因為Class有一些方法比如cast可以不用讓你suppress warning(我個人認為的)。上面的type.cast方法其實就是Java’s cast operator對應的“動態版本”,它只是檢查一下它的參數是不是被自己代表的類型,不是的話就拋出ClassCastException:
public class Class<T> {T cast(Object obj); }另外,說些沒什么關聯的事兒:如果你把Class<?>類型轉換成,比如Class<? extends Apple>,會得到warning,那么你可以用asSubclass這個方法,比如假設你得到了一個Class<?>類型的變量apple,然后你可以apple.asSubclass(Apple.class),意思就是“把Class<?>變成Class<Apple>”(反正你就這么理解吧),如果這個apple指向的對象并不是一個“Apple對象”的Class object,那就拋出異常。
轉載于:https://www.cnblogs.com/raytheweak/p/7190157.html
總結
以上是生活随笔為你收集整理的《Effective Java》读书笔记 - 5.泛型的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 幼儿园音乐教案《摇篮曲》教学设计与反思
- 下一篇: Java面试基础知识(1)