簡介
我們通常講到閉包,一般都是指在javascript的環境中。閉包是JS中一個非常重要的也非常常用的概念。閉包產生的原因就是變量的作用域范圍不同。一般來說函數內部的定義的變量只有函數內部可見。如果我們想要在函數外部操作這個變量就需要用到閉包了。
更多精彩內容且看:
- 區塊鏈從入門到放棄系列教程-涵蓋密碼學,超級賬本,以太坊,Libra,比特幣等持續更新
- Spring Boot 2.X系列教程:七天從無到有掌握Spring Boot-持續更新
- Spring 5.X系列教程:滿足你對Spring5的一切想象-持續更新
- java程序員從小工到專家成神之路(2020版)-持續更新中,附詳細文章教程
更多內容請訪問www.flydean.com
JS中的閉包
在JS中,變量可以分為兩種全局作用域和局部作用域。在函數外部無法讀取函數內部定義的局部變量。
function f1(){
var n=10;
}
alert(n); // error
上面的例子中,我們在函數f1中定義了一個局部變量n,然后嘗試從函數外部訪問它。結果出錯。
雖然函數中定義的變量在函數外部無法被訪問。但是在函數中定義的函數中可以訪問呀。
function f1(){
var n=10;
function f2(){
alert(n);
}
return f2;
}
var result=f1();
result(); // 10
上面的例子中,我們在f1中定義了f2,在f2中訪問了局部變量n。最后將f2返回。接着我們可以操作返回的函數f2來對函數中定義的局部變量n進行操作。
所以我們得出了閉包的定義:閉包就是定義在函數內部的函數,或者閉包是能夠訪問函數局部變量的函數。
java中的閉包
在lambda表達式出現之前,java中是沒有函數的概念的。和函數差不多相當的就是方法了。
在方法內部可以定義方法的局部變量。我們無法在方法內部定義方法,但是我們可以在方法內部定義匿名類。那么這個匿名類是可以訪問方法中定義的局部變量的。如下例所示:
public Runnable createClosureUsingClass(){
int count=10;
Runnable runnable= new Runnable() {
@Override
public void run() {
System.out.println(count);
}
};
return runnable;
}
在上面的方法中,我們定義了一個局部變量count。然后創建了一個匿名類runnable。在runnable中,我們訪問了局部變量count。
最后將這個創建的匿名類返回。這樣返回的匿名類就包含了對方法局部變量的操作,這樣就叫做閉包。
在Lambda表達式最佳實踐中,我們介紹了lambda表達式和匿名類的不同之處在於:
在內部類中,會創建一個新的作用域范圍,在這個作用域范圍之內,你可以定義新的變量,並且可以用this引用它。
但是在Lambda表達式中,並沒有定義新的作用域范圍,如果在Lambda表達式中使用this,則指向的是外部類。
雖然this的指向是不同的,但是在lambda表達式中也是可以訪問方法的局部變量:
public Runnable createClosureUsingLambda(){
int count=10;
Runnable runnable=()-> System.out.println(count);
return runnable;
}
上面的例子中,我們在lambda表達式中訪問了定義的count變量。
深入理解lambda表達式和函數的局部變量
首先lambda表達式是無狀態的,因為lambda表達式的本質是函數,它的作用就是在給定輸入參數的情況下,輸出固定的結果。
如果lambda表達式中引用的方法中的局部變量,則lambda表達式就變成了閉包,因為這個時候lambda表達式是有狀態的。我們接下來用個例子來具體說明。
上面的lambda表達式創建的Runnable,我們可以這樣使用:
public void testClosureLambda(){
Runnable runnable=createClosureUsingLambda();
runnable.run();
}
為了深入理解lambda表達式和局部變量傳值的關系,我們將編譯好的class文件進行反編譯。
javap -c -p ClosureUsage
將部分輸出結果列出如下:
public java.lang.Runnable createClosureUsingLambda();
Code:
0: bipush 10
2: istore_1
3: iload_1
4: invokedynamic #12, 0 // InvokeDynamic #0:run:(I)Ljava/lang/invokedynamicinvokedynamic;
9: astore_2
10: aload_2
11: areturn
private static void lambda$createClosureUsingLambda$0(int);
Code:
0: getstatic #29 // Field java/lang/System.out:Ljava/io/PrintStream;
3: iload_0
4: invokevirtual #35 // Method java/io/PrintStream.println:(I)V
7: return
上面我們列出了createClosureUsingLambda和它內部的lambda表達式的反編譯結果。
可以看到在createClosureUsingLambda方法中,我們首先定義了一個值為10的int,並將其入棧。
再看lambda表達式生成的方法,我們可以看到這個方法多出了一個int參數,並且通過getstatic命令將參數傳遞進來。
這就是lambda表達式傳遞狀態的原理。
總結
本文介紹了閉包和lambda表達式之間的關系,並從字節碼的角度進一步說明了局部變量是怎么傳遞給函數內部的lambda表達式的。
本文的例子https://github.com/ddean2009/
learn-java-base-9-to-20
本文作者:flydean程序那些事
本文鏈接:http://www.flydean.com/java-lambda-closure/
本文來源:flydean的博客
歡迎關注我的公眾號:程序那些事,更多精彩等着您!