Java 8 Lambda 表達式示例
自從我聽說Java8將要支持Lambda表達式(或稱閉包),我便開始狂熱的想要將這些體面的簡潔的功能元素應用到我的代碼中來。大多開發者普遍的使用匿名內部類來開發事件處理器,比較器,thread/runnable實現等等,一些沒有必要的輔助代碼將邏輯復雜化,即便一些非常簡單的代碼也變的復雜不堪。Java8現在加入了Lambda表達式作為語法的一部分將會極大地解決這一類似問題。
它使得開發者可以封裝一個單獨的行為單元并且傳遞給其他代碼。他更像是一個匿名類(帶有一個方法的可推斷類型)的語法糖和一個更少對象的方法。我希望這篇文章不會扯過多廣泛的理論材料,但是在理解Lambda的語法,結構和實例前,這里還是有一個非常重要的概念需要注意。
函數式接口
一個函數式接口 (又或者稱單抽象方法類型或者SMA)是任何包含為一個抽象方法的接口. 但它可能包含一些static或者/和default 方法.java.lang.Runnableis是一個函數式接口的例子, 因為他只有一個run()方法, 并且是抽象的. 類似的 ActionListener也是一個函數式接口. 下面是一個用戶自定義函數式接口的例子。
interface Worker() {boolean doWork(); };</pre>
喊一下另一個典型的函數式接口的例子:
interface Operator {TYPE operate(TYPE operand1, TYPE operand2); }</pre>
就是這樣, 因為他就是一個含有一個抽象方法的普通接口. 盡管對于函數式接口還有很多需要討論尤其是java8提供的 java.util.function包和@FunctionalInterface注解,但現在僅僅關注lambdas。我會在另一篇單獨的博客中討論那些話題。
Lambda 表達式
Lambda 表達式,也被稱為閉包,是為開發者提供用簡單和緊湊的方式表示數據的匿名函數。
</blockquote>- Brian Goetz, Specification Lead for JSR 335
為了更容易的了解lambda表達式的語法,我們先來看一下常規的匿名內部類。
new Runnable() {public void run() {
performWork(); } };</pre>
Lambda 表達式為匿名內部類提供了一個更簡潔的實現方法,上面的5行代碼可以被轉換成下面這一行代碼:
() -> performWork();語法和結構
所以,標準的lambda表達式的語法像下面這樣:
() -> some expression或者
(arguments) -> { body just like function }一個 lambda 表達式有以下三部分組成:
用括號包裹,并以逗號分割的參數列表
// 接受連個整數,并返回它們的和 (int x, int y) -> x + y// 用一個整數作為參數的lambda表達式,并且返回該整數的下一個整數 (int x) -> { return x + 1; }</pre>
在lambda表達式中,你可以省略參數的數據類型。
// 同樣的lambda表達式,但是沒有了數據類型 (x, y) -> x + y(x) -> { return x + 1; }</pre>
此外,如果只有一個參數的話,你甚至可以將括號省略掉
// 只有一個參數的lambda表達式,并且省略掉了括號 x -> { return x + 1; }</li>箭頭符號,->
//沒有參數,并且返回一個常數92的lambda表達式 () -> 92// 接受一個字符串作為參數,并且將其輸出在控制臺中 (String s) -> { System.out.println(s); }</pre>
</li>函數體(body)——包括至少一句表達式,或這一個表達式塊。在表達式中,主體部分被執行,并且返回。
// 一條語句的body,沒有必要使用花括號包裹或返回語句。 x -> x + 1// 簡單的lambda表達式,返回值為空 () -> System.out.println(“Hello World!”)</pre>
在代碼塊的形式中,body就像一個方法的body那樣被執行,并且返回語句將流程返回到匿名方法的調用處。
</li> </ol>好了,我們已經花了些時間來了解lambda表達式的語法,接下來,讓我們來看一些實際的例子吧。
Lambda 表達式的例子
為了更容易的了解lambda表達式,讓我們先來看幾個那lambda表達式和匿名內部類作比較的例子。在第一個例子中,我們會看到使用lambda表達式來實現一個 Comparator 接口。假設我們有一個Person 類,該類有一個name屬性,并且,我們創建了一個Person類型的數組,名為persons。
Arrays.sort(persons, new Comparator() {@Override public int compare(Person first, Person second) {
return first.getName().compareTo(second.getName()); } });
// 這也是一個標準的排序,但是有趣的是,它傳入的是一個lambda表達式,而不是一個Comparator類型的對象 Arrays.sort(persons,(first, second) -> first.getName().compareTo(second.getName()));</pre>
注意到5行的代碼被精簡到了1行,這就是lambda表達式比匿名內部類漂亮的地方。接下來,讓我們看一個用lambda表達式來實現 Runable 接口的例子。結果也是一樣。
Runnable printer = new Runnable() {@Override public void run() {
System.out.println("Hello, I’m inside runnable class..."); } };
printer.run();
printer = () -> System.out.println("Hello, I’m inside runnable lambda...");
printer.run();</pre>
為用戶自定義的接口寫lambda表達式也同樣是間非常簡單容易的事。下面的例子使用 Operator自定義接口,并且將lambda表達式的引用保存在了變量中,以便重用。
Operator addition = (op1, op2) -> op1 + op2;System.out.println("Addition result: " + addition.operate(2, 3));</pre>
為了更深入的理解,這里有一個使用了代碼塊的lambda表達式的例子。
interface GenericOperator {TYPE operate(TYPE ... operands); }
//使用了代碼塊的lambda表達式 GenericOperator multiply = numbers -> {
int result = 0;
for(int num : numbers) result *= num;
return result; };
System.out.println("Multiplication result: " + multiply.operate(2,3,4));</pre>
如上所示,代碼塊就如同一個普通方法的代碼塊,lambda表達式就如同一個方法,但是它更有意義。
什么時候使用Lambda表達式
Lambda表達式并不是匿名內部類的替代者,而是一個用來實現單一抽象方法的更好的方式。兩者都有自己的意義,并在各自特定的情況下使用。
Lambda 表達式用來實現單一的行為,并且該行為要被傳遞個其他的代碼。
</li>當只需要一個功能接口的簡單實例,而不需要類型,構造方法和一些相關的東西時,Lambda表達式比較適用。
</li>另一方面,只在需要新的字段和功能時使用匿名內部類。
</li> </ul>Lambda表達式和匿名內部類
匿名內部類會引入下一級作用域,而lambda表達式不會。在匿名內部類中可以出現和父類中相同名字的變量,但這在lambda表達式中會報錯,并且不允許這樣做。因為后者不會引入下一級作用域,并且父級作用域中的局部變量和方法可以被直接訪問。
</li>匿名內部類把 this 關鍵字作為它自身的對象,而lambda表達式把它當成是該表達式所在類的對象。在表達式所在的類中,你可以通過使用 this 來訪問lambda表達式中的變量。
</li>但是,像匿名內部類那樣,lambda表達式只能訪問所在類中用final修飾的變量。同樣的,如果要訪問非final修飾的變量則會報錯。
</li>只有當lambda表達式的參數和返回值都是Serializable類型時才允許將其序列化。就像不推薦序列化匿名內部類一樣,我們也不推薦序列化lambda表達式。
</li>Lambda表達式被編譯成其所在類的私有方法。java 7中引入的invokedynamic被用來動態的綁定方法。
</li> </ul>Source Code
下面是一個IntelliJ的項目,包含了該篇文章中所有例子的源碼。
下載源碼