Lambda表示式與匿名內部類的聯絡和區別

2020-07-16 10:05:20
Java Lambda 表示式的一個重要用法是簡化某些匿名內部類的寫法,因此它可以部分取代匿名內部類的作用。

Lambda 表示式與匿名內部類的相同點如下:
  • Lambda 表示式與匿名內部類一樣,都可以直接存取 effectively final 的區域性變數(如果不了解 Effectively final,可先閱讀《Java8新特性之Effectively final》一節),以及外部類的成員變數(包括範例變數和類變數)。
  • Lambda 表示式建立的物件與匿名內部類生成的物件一樣,都可以直接呼叫從介面中繼承的預設方法。

下面程式示範了 Lambda 表示式與匿名內部類的相似之處。
@FunctionalInterface
interface Displayable {
    // 定義一個抽象方法和預設方法
    void display();

    default int add(int a, int b) {
        return a + b;
    }
}

public class LambdaAndInner {
    private int age = 12;
    private static String name = "C語言中文網";

    public void test() {
        String url = "http://c.biancheng.net/";
        Displayable dis = () -> {
            // 存取的區域性變數
            System.out.println("url 區域性變數為:" + url);
            // 存取外部類的範例變數和類變數
            System.out.println("外部類的 age 範例變數為:" + age);
            System.out.println("外部類的 name 類變數為:" + name);
        };
        dis.display();
        // 呼叫dis物件從介面中繼承的add()方法
        System.out.println(dis.add(3, 5)); 
    }

    public static void main(String[] args) {
        LambdaAndInner lambda = new LambdaAndInner();
        lambda.test();
    }
}
輸出結果為:

url 區域性變數為:http://c.biancheng.net/
外部類的 age 範例變數為:12
外部類的 name 類變數為:C語言中文網
8

上面程式使用 Lambda 表示式建立了一個 Displayable 的物件,Lambda 表示式的程式碼塊中的程式碼第 19、21 和 22 行分別示範了存取“effectively final”的區域性變數、外部類的範例變數和類變數。從這點來看, Lambda 表示式的程式碼塊與匿名內部類的方法體是相同的。

與匿名內部類相似的是,由於 Lambda 表示式存取了 url 區域性變數,因此該區域性變數相當於有一個隱式的 final 修飾,因此同樣不允許對 url 區域性變數重新賦值。

當程式使用 Lambda 表示式建立了 Displayable 的物件之後,該物件不僅可呼叫介面中唯一的抽象方法,也可呼叫介面中的預設方法,如上面程式程式碼第 26 行所示。

Lambda 表示式與匿名內部類主要存在如下區別。
  • 匿名內部類可以為任意介面建立範例——不管介面包含多少個抽象方法,只要匿名內部類實現所有的抽象方法即可;但 Lambda 表示式只能為函數式介面建立範例。
  • 匿名內部類可以為抽象類甚至普通類建立範例;但 Lambda 表示式只能為函數式介面建立範例。
  • 匿名內部類實現的抽象方法的方法體允許呼叫介面中定義的預設方法;但 Lambda 表示式的程式碼塊不允許呼叫介面中定義的預設方法。

對於 Lambda 表示式的程式碼塊不允許呼叫介面中定義的預設方法的限制,可以嘗試對上面的 LambdaAndInner.java 程式稍做修改,在 Lambda 表示式的程式碼塊中增加如下一行:

// 嘗試呼叫介面中的預設方法,編譯器會報錯
System.out.println(add(3, 5));

雖然 Lambda 表示式的目標型別 Displayable 中包含了 add() 方法,但 Lambda 表示式的程式碼塊不允許呼叫這個方法;如果將上面的 Lambda 表示式改為匿名內部類的寫法,當匿名內部類實現 display() 抽象方法時,則完全可以呼叫這個 add() 方法,如下面程式碼所示。
public void test() {
    String url = "http://c.biancheng.net/";
    Displayable dis = new Displayable() {

        @Override
        public void display() {
            // 存取的區域性變數
            System.out.println("url 區域性變數為:" + url);
            // 存取外部類的範例變數和類變數
            System.out.println("外部類的 age 範例變數為:" + age);
            System.out.println("外部類的 name 類變數為:" + name);
            System.out.println(add(3, 5));
        }
    };
    dis.display();
}