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


https://blog.csdn.net/wjw521wjw521/article/details/77333820

 

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

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

示例代碼:

 

[java]  view plain  copy
 
  1. public class InnerFinalTest {  
  2.   
  3.     private static Test test0= null;  
  4.       
  5.     public static void main(String[] args) {  
  6.         new InnerFinalTest().method1();  
  7.         System.out.println("-------");  
  8.         test0.test();  
  9.     }  
  10.       
  11.     public void method1(){  
  12.           
  13.         final Test  test = new Test();    
  14.         test0 = new Test(){  
  15.             @Override  
  16.             public void test(){               
  17.                 System.out.println("匿名內部類:" + test);  
  18.                 Field[] field = this.getClass().getDeclaredFields();  
  19.                 for (int i = 0; i < field.length; i++) {  
  20.                     System.out.println(field[i].getName());  
  21.                 }     
  22.             }  
  23.           
  24.         };  
  25.           
  26.               
  27.         InnerFinalTest ift = new InnerFinalTest();  
  28.         ift.innerFinalTest(test0);  
  29.         System.out.println("外部直接訪問變量:"+ test);  
  30.     }  
  31.     public void innerFinalTest(Test test){  
  32.         test.test();          
  33.     }  
  34. }  

 

 

 

Test類無關緊要,不過還是貼一下他吧

 

 

[java]  view plain  copy
 
  1. public class Test {  
  2.     public void test(){  
  3.         System.out.println("啊啊啊啊啊!" );  
  4.     }  
  5. }  


說明:

 

為什么我們要將被匿名內部類訪問的變量定義成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。

 

上面實例代碼運行結果:

匿名內部類:Test@40e455bf
this$0
val$test
外部直接訪問變量:Test@40e455bf
-------
匿名內部類:Test@40e455bf
this$0
val$test

 

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

 

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

 

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

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


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2021 ITdaan.com