編譯器說 Lambda 表示式中的變數必須是 final 的,我偏不信

沉默王二發表於2020-04-07

偶爾,我們需要在 Lambda 表示式中修改變數的值,但如果直接嘗試修改的話,編譯器不會視而不見聽而不聞,它會警告我們說:“variable used in lambda expression should be final or effectively final”。

這個問題發生的原因是因為 Java 規範中是這樣規定的:

Any local variable, formal parameter, or exception parameter used but not declared in a lambda expression
must either be declared final or be effectively final (§4.12.4),
or a compile-time error occurs where the use is attempted.

大致的意思就是說,Lambda 表示式中要用到的,但又未在 Lambda 表示式中宣告的變數,必須宣告為 final 或者是 effectively final,否則就會出現編譯錯誤。

關於 final 和 effectively final 的區別,可能有些小夥伴不太清楚,這裡多說兩句。

final int a;
a = 1;
// a = 2;
// 由於 a 是 final 的,所以不能被重新賦值

int b;
b = 1;
// b 此後再未更改
// b 就是 effectively final

int c;
c = 1;
// c 先被賦值為 1,隨後又被重新賦值為 2
c = 2;
// c 就不是 effectively final

明白了 final 和 effectively final 的區別後,我們瞭解到,如果把 limit 定義為 final,那就無法在 Lambda 表示式中修改變數的值。那有什麼好的解決辦法呢?既能讓編譯器不發出警告,又能修改變數的值。

思前想後,試來試去,我終於找到了 3 個可行的解決方案:

1)把 limit 變數宣告為 static。

2)把 limit 變數宣告為 AtomicInteger。

3)使用陣列。

下面我們來詳細地一一介紹下。

01、把 limit 變數宣告為 static

要想把 limit 變數宣告為 static,就必須將 limit 變數放在 main() 方法外部,因為 main() 方法本身是 static 的。完整的程式碼示例如下所示。

public class ModifyVariable2StaticInsideLambda {
    static int limit = 10;
    public static void main(String[] args) {
        Runnable r = () -> {
            limit = 5;
            for (int i = 0; i < limit; i++) {
                System.out.println(i);
            }
        };
        new Thread(r).start();
    }
}

來看一下程式輸出的結果:

0
1
2
3
4

OK,該方案是可行的。

02、把 limit 變數宣告為 AtomicInteger

AtomicInteger 可以確保 int 值的修改是原子性的,可以使用 set() 方法設定一個新的 int 值,get() 方法獲取當前的 int 值。

public class ModifyVariable2AtomicInsideLambda {
    public static void main(String[] args) {
        final AtomicInteger limit = new AtomicInteger(10);
        Runnable r = () -> {
            limit.set(5);
            for (int i = 0; i < limit.get(); i++) {
                System.out.println(i);
            }
        };
        new Thread(r).start();
    }
}

來看一下程式輸出的結果:

0
1
2
3
4

OK,該方案也是可行的。

03、使用陣列

使用陣列的方式略帶一些欺騙的性質,在宣告陣列的時候設定為 final,但更改 int 的值時卻修改的是陣列的一個元素。

public class ModifyVariable2ArrayInsideLambda {
    public static void main(String[] args) {
        final int [] limits = {10};
        Runnable r = () -> {
            limits[0] = 5;
            for (int i = 0; i < limits[0]; i++) {
                System.out.println(i);
            }
        };
        new Thread(r).start();
    }
}

來看一下程式輸出的結果:

0
1
2
3
4

OK,該方案也是可行的。

04、鳴謝

好了,親愛的讀者朋友,以上就是本文的全部內容了,是不是感覺挺有意思的,編譯器告訴我們要用 final 修飾 Lambda 表示式外的變數,但我們卻找到了其他的解決方案,還一找就是 3 個,是不是感覺技能包又升級了,有沒有?伸出小手給自己點個贊?吧。

PS:本篇文章中的示例程式碼已經同步到碼雲,傳送門~

相關文章