Lambda表達式和閉包Closure


簡介

我們通常講到閉包,一般都是指在javascript的環境中。閉包是JS中一個非常重要的也非常常用的概念。閉包產生的原因就是變量的作用域范圍不同。一般來說函數內部的定義的變量只有函數內部可見。如果我們想要在函數外部操作這個變量就需要用到閉包了。

更多精彩內容且看:

更多內容請訪問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的博客

歡迎關注我的公眾號:程序那些事,更多精彩等着您!


免責聲明!

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



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