lambda 表示式學習筆記

2022-10-23 18:00:08

目錄

  • 函數式介面
  • lambda 表示式簡介
    • 理解lambda表示式——匿名內部類
    • lambda表示式語法
    • lambda行為引數化
  • lambda表示式的作用域
    • this 和 super ,lambda表示式真不是匿名內部類
    • 變數作用域
  • java8+中的函數式介面

函數式介面

如果一個Java介面類包含且僅包含一個無預設實現的方法定義,那麼他被稱為函數式介面。這個方法定義了介面的預期用途。
@FunctionalInterface註解指示一個介面類是一個函數介面,不允許有多個沒有預設實現的介面,但不強制要求在介面上新增該註解

範例1:定義一個函數式介面

@FunctionalInterface
public interface MyFunctionalInterface {
    // the single abstract method
    void function();
}

範例1中有且僅有沒有預設實現的方法定義——function(), 因此它是一個功能介面。

lambda 表示式簡介

理解lambda表示式——匿名內部類

lambda表示式本質上是一個匿名函數,用於實現一個函數介面定義的功能(不能理解的話可以看作是一個匿名物件,後面會專門說區別)。

lambda表示式語法

lambda表示式的一般語法是:

(Parameters) -> { Body }

Parameters為函數的入參,放在()中,與方法定義相同,lambda表示式的實現放在{}

注意

  • 引數的型別宣告是可選的,編譯器可以統一識別引數型別。明確宣告引數型別的lambda表示式稱為顯示lambda表示式,反之稱之為隱式lambda表示式
  • ()可選的,當引數有且僅有一個時,可以省略小括號,否則必須保留
  • {}可選的,如果實現的主題只有一條語句,則可以省略,反之必須保留
  • 返回關鍵字retuen可選的,在省略{}的同時,如果表示式有返回值可以省略關鍵字retuen

範例2:

// 給定一個值x,返回其的3倍
(int x) ->{return x*3;};
// 省略可選項後定義如下:
x -> x * 3;

lambda行為引數化

可以將lambda表示式作為引數傳遞給方,就像傳遞一個普通的物件一樣
範例3:

public class Test {
    public static void main(String[] argv) {
        doWork((x,y)-> x + y, 4, 2);
    }

    // (x,y)-> x + y  即為Function 函數介面的實現
    private static int doWork(Function function, int x, int y){
        return function.cal(x,y);
    }

    // 定義一個函數介面, 對兩個整形進行計算
    @FunctionalInterface
    interface Function{
        int cal(int x, int y);
    }
}

lambda表示式的作用域

this 和 super ,lambda表示式真不是匿名內部類

lambda表示式使用時不會生成新的物件,也就不會產生自己的範圍。
如果lambda表示式中使用關鍵字thissuper,他的行為與在執行lambda表示式的方法中使用thissuper的行為完全相同。

範例4:

public class Test {
    public static void main(String[] argv) {
        Test test = new Test();
        test.test();
    }
    private void test(){
        MyFunctionalInterface functionNewClass = new MyFunctionalInterface(){
            @Override
            public void function(int x) {
                System.out.println("匿名內部類this=======   "+ this);
            }
        };
        MyFunctionalInterface functionLambda = x -> System.out.println("lambda表示式中執行this ==========  " + this);
        System.out.println("方法中執行this=======   "+ this);
        functionNewClass.function(1);
        functionLambda.function(1);
    }
}


// 執行結果
方法中執行this=======   cn.bmilk.Test@4bf558aa
匿名內部類this=======   cn.bmilk.Test$1@2d38eb89
lambda表示式中執行this ==========  cn.bmilk.Test@4bf558aa

範例3的結果顯示,lambda表示式中呼叫this指標和在方法test()中呼叫this指代的是同一個物件cn.bmilk.Test@4bf558aa, 而使用匿名內部類this指代的是當前匿名內部類產生的物件cn.bmilk.Test$1@2d38eb89,所以lambda表示式和匿名內部類是不同的

變數作用域

lambda表示式可以使用`標記了`final的外層區域性變數,這就是說在lambda表示式中不能修改外層定義的區域性變數(不包含全域性變數),否則會編譯錯誤。

範例5:參照外層區域性變數

    private void test(){
        int num = 2;
        MyFunctionalInterface functionLambda = x -> System.out.println(x * num);
        functionLambda.function(1);
    }

範例5:test*()中定義了一個變數num,並且在functionLambda中進行了參照。前面提到lambda表示式使用外層區域性變數,外層區域性變數需要定義為final,但這裡並沒又定義為final,這是因為num雖然沒有被定義為final,但是在num被定義之後並沒有再次改變,看起來和一個被final修飾的變數一樣,即含有隱性的final定義。
當嘗試再後面修改num的值,編譯器會提示錯誤資訊,如範例6

範例6:

    private void test(){
        int num = 2;
        num = 3;
        // 編譯器提示 參照外層區域性變數,外層區域性變數必須為final或者等同於final
        // Variable used in lambda expression should be final or effectively final
        MyFunctionalInterface functionLambda = x -> System.out.println(x * num);
        functionLambda.function(1);
    }

之所以參照外層變數必須是final是因為,區域性變數被定義再棧幀中,java存取區域性變數的時候實際上是存取他的副本,多個執行緒對應了多個棧,當lambda表示式執行執行緒與定義執行緒不是同一個情況下,如果變數不被定義為final,當一個執行緒改變了變數值,另一個有可能會讀取不到最新值導致出錯。

java8+中的函數式介面

java8及更高版本內建了一系列的函數式介面,被放在java.util.function包下,用於支援j函數語言程式設計。另外在之前的版本中存在的java.lang.Runnablejava.util.Comparator也都可以用於函數語言程式設計