[Java] Local variable xxxx defined in an enclosing scope must be final or effectively final

以下程式在編譯時期會出現錯誤訊息

interface Greeting {
    void hi();
}

public static void main(String[] args) {
    String name = "John";
    name += " and Marry";

    Greeting greeting = () -> {
        System.out.println("Hello " + name);
    };

    greeting.hi();
}

原因

這好發於 Anonymous Inner Class

局部類別存取的外部變數
必須是 final 或等效於 final

而以上程式
Greeting 所存取的 name 變數
經過修改所以不符規定

解決辦法

傳入:重新引用

取得外部資料的部分不難
只要將修改的變數重新引用即可

在外部重新引用:

String name = "John";
String newName = name + " and Marry";

Greeting greeting = () -> {
    System.out.println("Hello " + newName);
};

greeting.hi();

在內部重新引用:

String name = "John";

Greeting greeting = () -> {
    String newName = name + " and Marry";
    System.out.println("Hello " + newName);
};

greeting.hi();

傳出:使用容器

如果不只傳入資料
還要將運行後的結果返回給外部
就必須使用容器

例如以下程式
雖然重新引用可以在內部順利運作
但外部就無法取得加總後的結果

int sum = 0;
List<Integer> numbers = Arrays.asList(23, 45, 67, 89);
numbers.forEach(i -> {
    sum += i;
});
System.out.println(sum);

可以藉由容器的存入取出
修改其中的資料
而容器本身是同一個
所以能實現內部外部的溝通
程式調整後如下:

List<Integer> sum = new ArrayList<Integer>();
sum.add(0);

List<Integer> numbers = Arrays.asList(23, 45, 67, 89);
numbers.forEach(i -> {
    sum.set(0, sum.get(0) + i);
});

System.out.println(sum.get(0));

以上程式可以使用 AtomicInteger 達到相同效果

AtomicInteger sum = new AtomicInteger(0);

List<Integer> numbers = Arrays.asList(23, 45, 67, 89);
numbers.forEach(i -> {
    sum.addAndGet(i);
});

System.out.println(sum.get());

其實也是物件承載的概念
但 java.util.concurrent.atomic.* 只限定某些類型
因此還是最泛用