1. 程式人生 > >匿名內部類訪問方法成員變數需要加final的原因及證明

匿名內部類訪問方法成員變數需要加final的原因及證明

在java程式設計中,沒用的類定義太多對系統來說也是一個負擔,這時候我們可以通過定義匿名內部類來簡化程式設計,但匿名內部類訪問外部方法的成員變數時都要求外部成員變數新增final修飾符,final修飾變數代表該變數只能被初始化一次,以後不能被修改。但為什麼匿名內部類訪問外部成員變數就不允許他修改了呢?

接下來這個例子應該足夠把這些說清楚了:

示例程式碼:

public class InnerFinalTest {

	private static Test test0= null;
	
	public static void main(String[] args) {
		new InnerFinalTest().method1();
		System.out.println("-------");
		test0.test();
	}
	
	public void method1(){
		
		final Test  test = new Test();	
		test0 = new Test(){
			@Override
			public void test(){				
				System.out.println("匿名內部類:" + test);
				Field[] field = this.getClass().getDeclaredFields();
				for (int i = 0; i < field.length; i++) {
					System.out.println(field[i].getName());
				}	
			}
		
		};
		
			
		InnerFinalTest ift = new InnerFinalTest();
		ift.innerFinalTest(test0);
		System.out.println("外部直接訪問變數:"+ test);
	}
	public void innerFinalTest(Test test){
		test.test();		
	}
}
Test類無關緊要,不過還是貼一下他吧
public class Test {
	public void test(){
		System.out.println("啊啊啊啊啊!" );
	}
}

說明:

為什麼我們要將被匿名內部類訪問的變數定義成final呢?
首先,我們在InnerFinalTest類中定義了一個static變數test0:
private static Test test0= null;
該語句說明test0的生命週期和類一樣
接下來在main方法中呼叫method1(),在method1()中將我們定義的匿名內部類賦給了test0,這說明如果test0不往別處指的話,我們匿名內部類將被一直引用著,
如同吃了九轉大金丹,與天地同壽,與日月齊光,匿名內部類生命週期和InnerFinalTest類(匿名類的天地)相同了。
但是,method1()呼叫完了他要釋放資源了,所以method1()方法中:

final Test  test = new Test();

test變數也要被釋放了,test沒了,但匿名內部類引用了test,如果java編譯器不搞點小動作,他就沒法玩兒了,因為匿名類的生命週期長,還使用著test,而外部變數先撤了,背後捅了匿名內部類一刀子。。。
匿名內部類說,就防著你這一招呢,所以叫編譯器大哥幫忙搞了個小動作,明修棧道暗度陳倉,編譯的時候,我自己把你給我的變數備份了一份,表面上看是我引用了你的變數,其實在執行期間我就用我自己備份的了。但是別人表面上看不知道我備份了一份,還以為我用的你的,如果不定義成final,變數在外面被修改了,我沒改,那我的結果就會和預期不同,為了防止出現這種情況,所以要被定義成final。

上面例項程式碼執行結果:

匿名內部類:[email protected]
this$0
val$test
外部直接訪問變數:[email protected]
-------
匿名內部類:[email protected]
this$0
val$test

我用反射證明了匿名內部類存在外部變數的備份val$test,其中因為變數是預設型別,所以使用getDeclaredFields得到所有匿名內部類執行期間存在的成員屬性,注意,該成員屬性在編碼期間是不存在的,
是編譯器主動為匿名內部類新增的成員屬性,所以可以通過反射在執行期間一窺究竟。

如果去掉匿名內部類對外部變數的引用,如去掉以下程式碼:
System.out.println("匿名內部類:" + test);
執行結果中會沒有了val$test,這也再次證明了以上結論:匿名內部類備份了變數

通過外部變數和內部變數列印內容相同,說明兩個變數test和val$test的變數引用指向的記憶體區域是相同的,(這裡可以參考一下原型模式淺克隆)。指向相同物件,雖然物件不能修改,但物件中的屬性可以修改,而匿名內部類變數和外部變數指向相同,自然值也同步修改了。

總結一下,邏輯應該是這樣的:為了解決生命週期不同的問題,匿名內部類備份了變數,為了解決備份變數引出的問題,外部變數要被定義成final
我們匿名內部類使用final不是怕修改,是怕不能同步修改

如有錯誤,歡迎指正

end