歡迎光臨
每天分享高質量文章

lambda 運算式和閉包

(點選上方公眾號,可快速關註)


來源:黃億華,

my.oschina.net/flashsword/blog/161413?fromerr=XEakuEco

區分lambda運算式和閉包

熟悉的Javascript或者Ruby的同學,可能對另一個名詞:閉包更加熟悉。因為一般閉包的示例程式碼,長得跟lambda差不多,導致我也在以前很長一段時間對這兩個概念傻傻分不清楚。其實呢,這兩個概念是完全不同維度的東西。

閉包是個什麼東西呢?我覺得Ruby之父松本行弘在《程式碼的未來》一書中解釋的最好:閉包就是把函式以及變數包起來,使得變數的生存週期延長。閉包跟面向物件是一棵樹上的兩條枝,實現的功能是等價的。

這樣說可能不夠直觀,我們還是用程式碼說話吧。其實Java在很早的版本就支援閉包了,只是因為應用場景太少,這個概念一直沒得到推廣。在Java6裡,我們可以這樣寫:

public static Supplier testClosure(){

 final int i = 1;

 return new Supplier() {

 @Override

 public Integer get() {

 return i;

 }

 };

}

public interface Supplier {

 T get();

}

看出問題了麼?這裡i是函式testClosure的內部變數,但是最終傳回裡的匿名物件裡,仍然傳回了i。我們知道,函式的區域性變數,其作用域僅限於函式內部,在函式結束時,就應該是不可見狀態,而閉包則將i的生存週期延長了,並且使得變數可以被外部函式所取用。這就是閉包了。這裡,其實我們的lambda運算式還沒有出現呢!

而支援lambda運算式的語言,一般也會附帶著支援閉包了,因為lambda總歸在函式內部,與函式區域性變數屬於同一陳述句塊,如果不讓它取用區域性變數,不會讓人很彆扭麼?例如Python的lambda定義我覺得是最符合λ運算元的形式的,我們可以這樣定義lambda:

#!/usr/bin/python

y = 1

f=lambda x: x + y

print f(2)

y = 3

print f(2)

輸出: 

3

5

這裡y其實是外部變數。

Java中閉包帶來的問題

在Java的經典著作《Effective Java》、《Java Concurrency in Practice》裡,大神們都提到:匿名函式裡的變數取用,也叫做變數取用洩露,會導致執行緒安全問題,因此在Java8之前,如果在匿名類內部取用函式區域性變數,必須將其宣告為final,即不可變物件。(Python和Javascript從一開始就是為單執行緒而生的語言,一般也不會考慮這樣的問題,所以它的外部變數是可以任意修改的)。

在Java8裡,有了一些改動,現在我們可以這樣寫lambda或者匿名類了:

public static Supplier testClosure() {

 int i = 1;

 return () -> {

 return i;

 };

}

這裡我們不用寫final了!但是,Java大神們說的取用洩露怎麼辦呢?其實呢,本質沒有變,只是Java8這裡加了一個語法糖:在lambda運算式以及匿名類內部,如果取用某區域性變數,則直接將其視為final。我們直接看一段程式碼吧:

public static Supplier testClosure() {

 int i = 1;

 i++;

 return () -> {

 return i; //這裡會出現編譯錯誤

 };

}

明白了麼?其實這裡我們僅僅是省去了變數的final定義,這裡i會強制被理解成final型別。很搞笑的是編譯錯誤出現在lambda運算式內部取用i的地方,而不是改變變數值的i++…這也是Java的lambda的一個被人詬病的地方。我只能說,強制閉包裡變數必須為final,出於嚴謹性我還可以接受,但是這個語法糖有點酸酸的感覺,還不如強制寫final呢…

看完本文有收穫?請轉發分享給更多人

關註「ImportNew」,提升Java技能

贊(0)

分享創造快樂