1. 程式人生 > >內部類中引用的變數必須要宣告為final的原因

內部類中引用的變數必須要宣告為final的原因

查了下看到有人說原因如下:

區域性匿名類在原始碼編譯後也是要生成對應的class檔案的(一般會是A$1.class這種形式的檔案),那麼這個二進位制檔案是獨立於其外圍類(A.class)的,就是說它無法知道A類中方法的變數。但是A$1.class又確實要訪問A類對應方法的區域性變數的值。。。怎麼辦呢?於是乾脆就要求“匿名內部類呼叫的方法內區域性變數必須為final”,這樣A$1.class訪問A類方法區域性變數部分就直接用常量來表示
這是一個編譯器設計的問題,如果你瞭解java的編譯原理的話很容易理解。
首先,內部類被編譯的時候會生成一個單獨的內部類的.class檔案,這個檔案並不與外部類在同一class檔案中。
當外部類傳的引數被內部類呼叫時,從java程式的角度來看是直接的呼叫例如:
public void dosome(final String a,final int b){
class Dosome{public void dosome(){System.out.println(a+b)}};
Dosome some=new Dosome();
some.dosome();
}
從程式碼來看好像是那個內部類直接呼叫的a引數和b引數,但是實際上不是,在java編譯器編譯以後實際的操作程式碼是
class Outer$Dosome{
public Dosome(final String a,final int b){
this.Dosome$a=a;
this.Dosome$b=b;
}
public void dosome(){
System.out.println(this.Dosome$a+this.Dosome$b);
}
}}
從以上程式碼看來,內部類並不是直接呼叫方法傳進來的引數,而是內部類將傳進來的引數通過自己的構造器備份到了自己的內部,自己內部的方法呼叫的實際是自己的屬性而不是外部類方法的引數。
這樣理解就很容易得出為什麼要用final了,因為兩者從外表看起來是同一個東西,實際上卻不是這樣,如果內部類改掉了這些引數的值也不可能影響到原引數,然而這樣卻失去了引數的一致性,因為從程式設計人員的角度來看他們是同一個東西,如果程式設計人員在程式設計的時候在內部類中改掉引數的值,但是外部呼叫的時候又發現值其實沒有被改掉,這就讓人非常的難以理解和接受,為了避免這種尷尬的問題存在,所以編譯器設計人員把內部類能夠使用的引數設定為必須是final來規避這種莫名其妙錯誤的存在。

JVM中每個程序都會有多個根,每個static變數,方法引數,區域性變數,當然這都是指引用型別.基礎型別是不能作為根的,根其實就是一個儲存地址.垃圾回收器在工作時先從根開始遍歷它引用的物件並標記它們,如此遞迴到最末梢,所有根都遍歷後,沒有被標記到的物件說明沒有被引用,那麼就是可以被回收的物件(有些物件有finalized方法,雖然沒有引用,但JVM中有一個專門的佇列引用它們直到finalized方法被執行後才從該佇列中移除成為真正沒有引用的物件,可以回收,這個與本主題討論的無關,包括代的劃分等以後再說明).這看起來很好但是在內部類的回撥方法中,s既不可能是靜態變數,也不是方法中的臨時變數,也不是方法引數,它不可能作為根,在內部類中也沒有變數引用它,它的根在內部類外部的那個方法中,如果這時外面變數s重指向其它物件,則回撥方法中的這個物件s就失去了引用,可能被回收,而由於內部類回撥方法大多數在其它執行緒中執行,可能還要在回收後還會繼續訪問它.這將是什麼結果?而使用final修飾符不僅會保持物件的引用不會改變,而且編譯器還會持續維護這個物件在回撥方法中的生命週期.所以這才是final變數和final引數的根本意義.