1. 程式人生 > >Java進階——Java 區域性內部類訪問區域性變數為什麼必須加final關鍵字

Java進階——Java 區域性內部類訪問區域性變數為什麼必須加final關鍵字

Java 區域性內部類訪問區域性變數為什麼必須加final關鍵字

疑問

在Java中,區域性內部類如果呼叫了方法中的變數,那麼該變數必須申明為final型別,如果不申明,則編譯就會出錯。

這裡的內部類指的是方法內部類或匿名內部類,不包含靜態內部類和成員內部類

這裡通過一個例子類分析

public class InnerClass {
  
	private int defaultAge = 5;
  區域性變數 age,必須新增final關鍵字,這裡先不加
	public void addAge( int age){
  
    //區域性內部類
		class NewAge{
			private int getAge(){
				return age + defaultAge;
			}
		}

		NewAge newAge = new NewAge();
		System.out.print(newAge.getAge());
	}
}

強行不加final,編譯,則會報錯:

Error:(16, 12) 錯誤: 從內部類中訪問本地變數age; 需要被宣告為最終型別

分析

  • 原因

1.生命週期不同: 為什麼必須區域性變數加final關鍵字呢?因為區域性變數直接儲存在棧中,當方法執行結束,非final的區域性變數就被銷燬,而區域性內部類對區域性變數的引用依然存在,當局部內部類要呼叫區域性變數時,就會出錯,出現非法引用。簡單來說,就是非final的區域性變數的生命週期比區域性內部類的生命週期短,是不是直接可以拷貝變數到區域性內部類?這樣內部類中就可以使用而且不擔心生命週期問題呢?也是不可以的,因為直接拷貝又會出現第二個問題,就是資料不同步
2.資料不同步

:內部類並不是直接使用傳遞進來的引數,而是將傳遞進來的引數通過自己的構造器備份到自己內部,表面看是同一個變數,實際呼叫的是自己的屬性而不是外部類方法的引數,如果在內部類中,修改了這些引數,並不會對外部變數產生影響,僅僅改變區域性內部類中備份的引數。但是在外部呼叫時發現值並沒有被修改,這種問題就會很尷尬,造成資料不同步。所以使用final避免資料不同步的問題

  • 原理

那為什麼新增final修飾的區域性變數,就可以被區域性內部類引用呢?
若定義為final,則java編譯器則會在內部類NewAge內生成一個外部變數的拷貝,而且可以既可以保證內部類可以引用外部屬性,又能保證值的唯一性
也就是拷貝了一個變數的副本,提供給區域性內部類,這個副本的生命週期和區域性內部類一樣長,並且這個副本不可以修改,保證了資料的同步


注意在Java8 中,被區域性內部類引用的區域性變數,預設新增final,所以不需要新增final關鍵詞

  • 位元組碼

如果有興趣,可以看看編譯後的位元組碼,即.class檔案

class InnerClass$1NewAge {
    //可以看到,區域性內部類中的使用的age,是通過建構函式傳遞進來,並不是直接引用外部變數。
    InnerClass$1NewAge(InnerClass var1, int var2) {
        this.this$0 = var1;
        this.val$age = var2;
    }

    private int getAge() {
        return this.val$age + InnerClass.access$000(this.this$0);
    }
}

InnerClass類編譯後,在資料夾會出現InnerClass.class和InnerClass$1NewAge.class,這說明外部類的方法 和內部類處於同一級。

結論

區域性內部類引用區域性變數,不新增final,會出現生命週期不同,導致非法引用問題,而且直接拷貝會出現資料不同步問題,所以使用final,保證了合法引用,而且資料不可修改