網頁

2020/10/10

Java 8 effectively final

Java 8開始有一個新的名詞叫effectively final的意思如下。,

所謂的effectively final變數是指雖然前面沒有final修飾,但宣告初值後就不再被改變的變數,官方說明如下。

A variable or parameter whose value is never changed after it is initialized is effectively final


如果本地類別(local class)及內部類別(inner class)要存取外部區塊(enclosing block)的變數,則該變數必須為final或effectively final變數才可以正確編譯。

例如下面demo()方法宣告變數n及初值後又被n++修改,因此n不是effectively final變數,在內部類別中存取n時會編譯錯誤。

void demo() {

    int n = 0; // 宣告變數n
    Thread t = new Thread(new Runnable() { // 匿名內部類別
        @Override
        public void run() {
            n++; // n的內容被修改,所以n不是effectively final
            System.out.println(n); // n編譯錯誤
        }
    });
    t.start();
}

若將n++移除,則n宣告後就不被修改屬於effectively final,所以內部類別可以存取。

void demo() {

    int n = 0; // 宣告變數n
    Thread t = new Thread(new Runnable() { // 匿名內部類別
        @Override
        public void run() {
            System.out.println(n); // n編譯正確
        }
    });
    t.start();
}

這點在使用lambda forEach()時要注意。過去用for each遍歷陣列或集合物件時常會引用外層區塊的物件並修改,例如

int n = 0;

List<Employee> employeeList = getEmployeeList(int age);

for (Employee e : employeeList) {
    n++; // 編譯正確

    // ...
}

但由於forEach()的傳入參數其實是一個實作Consumer的內部匿名類別,所以只能引用外部區塊的final及effectively final的變數。

int n = 0; // 宣告變數n及初值

List<Employee> employeeList = getEmployeeList(int age);

employeeList.forEach(e -> {
    n++; // 編譯錯誤
    
    // ...
});


沒有留言:

張貼留言