lambda表達式和閉包


區分lambda表達式和閉包

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

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

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

public static Supplier<Integer> testClosure(){
 final int i = 1;
 return new Supplier<Integer>() {
 @Override
 public Integer get() {
 return i;
 }
 };
}
public interface Supplier<T> {
 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<Integer> testClosure() {
 int i = 1;
 return () -> {
 return i;
 };
}

這里我們不用寫final了!但是,Java大神們說的引用泄露怎么辦呢?其實呢,本質沒有變,只是Java8這里加了一個語法糖:在lambda表達式以及匿名類內部,如果引用某局部變量,則直接將其視為final。我們直接看一段代碼吧:

public static Supplier<Integer> testClosure() {
 int i = 1;
 i++;
 return () -> {
 return i; //這里會出現編譯錯誤
 };
}

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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM