Tips
《Effective Java, Third Edition》一書英文版已經出版,這本書的第二版想必不少人都讀過,號稱Java四大名著之一,不過第二版2009年出版,到如今已經將近8年的時間,但隨着Java 6,7,8,甚至9的發佈,Java語言發生了深入的變化。
在這裏第一時間翻譯成中文版。供你們學習分享之用。java
在Java 8中,添加了函數式接口,lambda表達式和方法引用,以便更容易地建立函數對象。 Stream API隨着其餘語言的修改一同被添加進來,爲處理數據元素序列提供類庫支持。 在本章中,咱們將討論如何充分利用這些功能。程序員
以往,使用單一抽象方法的接口(或者不多使用抽象類)被用做函數類型。 它們的實例(稱爲函數對象)表示函數(functions)或行動(actions)。 自從JDK 1.1於1997年發佈以來,建立函數對象的主要手段就是匿名類(條目 24)。 下面是一段代碼片斷,按照字符串長度順序對列表進行排序,使用匿名類建立排序的比較方法(強制排序順序):express
// Anonymous class instance as a function object - obsolete! Collections.sort(words, new Comparator<String>() { public int compare(String s1, String s2) { return Integer.compare(s1.length(), s2.length()); } });
匿名類適用於須要函數對象的經典面向對象設計模式,特別是策略模式[Gamma95]。 比較器接口表示排序的抽象策略; 上面的匿名類是排序字符串的具體策略。 然而,匿名類的冗長,使得Java中的函數式編程成爲一種吸引人的前景。編程
在Java 8中,語言形式化了這樣的概念,即便用單個抽象方法的接口是特別的,應該獲得特別的對待。 這些接口如今稱爲函數式接口,而且該語言容許你使用lambda表達式或簡稱lambda來建立這些接口的實例。 Lambdas在功能上與匿名類類似,但更爲簡潔。 下面的代碼使用lambdas替換上面的匿名類。 樣板不見了,行爲清晰明瞭:設計模式
// Lambda expression as function object (replaces anonymous class) Collections.sort(words, (s1, s2) -> Integer.compare(s1.length(), s2.length()));
請注意,代碼中不存在lambda(Comparator <String>
),其參數(s1和s2,都是String類型)及其返回值(int)的類型。 編譯器使用稱爲類型推斷的過程從上下文中推導出這些類型。 在某些狀況下,編譯器將沒法肯定類型,必須指定它們。 類型推斷的規則很複雜:他們在JLS中佔據了整個章節[JLS,18]。 不多有程序員詳細瞭解這些規則,但不要緊。 除非它們的存在使你的程序更清晰,不然省略全部lambda參數的類型。 若是編譯器生成一個錯誤,告訴你它不能推斷出lambda參數的類型,那麼指定它。 有時你可能不得不強制轉換返回值或整個lambda表達式,但這不多見。app
關於類型推斷須要注意一點。 條目26告訴你不要使用原始類型,條目29告訴你偏好泛型類型,條目30告訴你偏向泛型方法。 當使用lambda表達式時,這個建議是很是重要的,由於編譯器得到了大部分容許它從泛型進行類型推斷的類型信息。 若是你沒有提供這些信息,編譯器將沒法進行類型推斷,你必須在lambdas中手動指定類型,這將大大增長它們的冗餘度。 舉例來講,若是變量被聲明爲原始類型List而不是參數化類型List <String>
,則上面的代碼片斷將不會編譯。ide
順便提一句,若是使用比較器構造方法代替lambda,則代碼中的比較器能夠變得更加簡潔(條目14,43):函數式編程
Collections.sort(words, comparingInt(String::length));
實際上,經過利用添加到Java 8中的List接口的sort方法,可使片斷變得更簡短:函數
words.sort(comparingInt(String::length));
將lambdas添加到該語言中,使得使用函數對象在之前沒有意義的地方很是實用。例如,考慮條目34中的Operation
枚舉類型。因爲每一個枚舉都須要不一樣的應用程序行爲,因此咱們使用了特定於常量的類主體,並在每一個枚舉常量中重寫了apply方法。爲了刷新你的記憶,下面是以前的代碼:學習
// Enum type with constant-specific class bodies & data public enum Operation { PLUS("+") { public double apply(double x, double y) { return x + y; } }, MINUS("-") { public double apply(double x, double y) { return x - y; } }, TIMES("*") { public double apply(double x, double y) { return x * y; } }, DIVIDE("/") { public double apply(double x, double y) { return x / y; } }; private final String symbol; Operation(String symbol) { this.symbol = symbol; } @Override public String toString() { return symbol; } public abstract double apply(double x, double y); }
第34條目說,枚舉實例屬性比常量特定的類主體更可取。 Lambdas能夠很容易地使用前者而不是後者來實現常量特定的行爲。 僅僅將實現每一個枚舉常量行爲的lambda傳遞給它的構造方法。 構造方法將lambda存儲在實例屬性中,apply方法將調用轉發給lambda。 由此產生的代碼比原始版本更簡單,更清晰:
public enum Operation { PLUS ("+", (x, y) -> x + y), MINUS ("-", (x, y) -> x - y), TIMES ("*", (x, y) -> x * y), DIVIDE("/", (x, y) -> x / y); private final String symbol; private final DoubleBinaryOperator op; Operation(String symbol, DoubleBinaryOperator op) { this.symbol = symbol; this.op = op; } @Override public String toString() { return symbol; } public double apply(double x, double y) { return op.applyAsDouble(x, y); } }
請注意,咱們使用表示枚舉常量行爲的lambdas的DoubleBinaryOperator
接口。 這是java.util.function
中許多預約義的函數接口之一(條目 44)。 它表示一個函數,它接受兩個double類型參數並返回double類型的結果。
看看基於lambda的Operation
枚舉,你可能會認爲常量特定的方法體已經失去了它們的用處,但事實並不是如此。 與方法和類不一樣,lambda沒有名稱和文檔; 若是計算不是自解釋的,或者超過幾行,則不要將其放入lambda表達式中。 一行代碼對於lambda說是理想的,三行代碼是合理的最大值。 若是違反這一規定,可能會嚴重損害程序的可讀性。 若是一個lambda很長或很難閱讀,要麼找到一種方法來簡化它或重構你的程序來消除它。 此外,傳遞給枚舉構造方法的參數在靜態上下文中進行評估。 所以,枚舉構造方法中的lambda表達式不能訪問枚舉的實例成員。 若是枚舉類型具備難以理解的常量特定行爲,沒法在幾行內實現,或者須要訪問實例屬性或方法,那麼常量特定的類主體仍然是行之有效的方法。
一樣,你可能會認爲匿名類在lambda時代已通過時了。 這更接近事實,但有些事情你能夠用匿名類來作,而卻不能用lambdas作。 Lambda僅限於函數式接口。 若是你想建立一個抽象類的實例,你可使用匿名類來實現,但不能使用lambda。 一樣,你可使用匿名類來建立具備多個抽象方法的接口實例。 最後,lambda不能得到對自身的引用。 在lambda中,this關鍵字引用封閉實例,這一般是你想要的。 在匿名類中,this關鍵字引用匿名類實例。 若是你須要從其內部訪問函數對象,則必須使用匿名類。
Lambdas與匿名類共享沒法可靠地序列化和反序列化實現的屬性。所以,應該不多(若是有的話)序列化一個lambda(或一個匿名類實例)。若是有一個想要進行序列化的函數對象,好比一個Comparator,那麼使用一個私有靜態嵌套類的實例(條目 24)。
綜上所述,從Java 8開始,lambda是迄今爲止表示小函數對象的最佳方式。 除非必須建立非函數式接口類型的實例,不然不要使用匿名類做爲函數對象。 另外,請記住,lambda表達式使表明小函數對象變得如此簡單,以致於它爲功能性編程技術打開了一扇門,這些技術在Java中之前並不實用。