java學習記錄(四):關於匿名內部類和局部內部類只能訪問final變量的問題

轉:http://feiyeguohai.iteye.com/blog/1500108

爲什麼匿名內部類參數必須爲final類型
1)  從程序設計語言的理論上:局部內部類(即:定義在方法中的內部類),由於本身就是在方法內部(可出現在形式參數定義處或者方法體處),因而訪問方法中的局部變量(形式參數或局部變量)是天經地義的.是很自然的

2)  爲什麼JAVA中要加上一條限制:只能訪問final型的局部變量?

3)  JAVA語言的編譯程序的設計者當然全實現:局部內部類能訪問方法中的所有的局部變量(因爲:從理論上這是很自然的要求),但是:編譯技術是無法實現的或代價極高.

4)  困難在何處?到底難在哪兒?
     局部變量的生命週期與局部內部類的對象的生命週期的不一致性!

5)  設方法f被調用,從而在它的調用棧中生成了變量i,此時產生了一個局部內部類對象inner_object,它訪問了該局部變量i .當方法f()運行結束後,局部變量i就已死亡了,不存在了.但:局部內部類對象inner_object還可能   一直存在(只能沒有人再引用該對象時,它纔會死亡),它不會隨着方法f()運行結束死亡.這時:出現了一個"荒唐"結果:局部內部類對象inner_object要訪問一個已不存在的局部變量i!

6)  如何才能實現?當變量是final時,通過將final局部變量"複製"一份,複製品直接作爲局部內部中的數據成員.這樣:當局部內部類訪問局部變量時,其實真正訪問的是這個局部變量的"複製品"(即:這個複製品就代表了那個局部變量).因此:當運行棧中的真正的局部變量死亡時,局部內部類對象仍可以訪問局部變量(其實訪問的是"複製品"),給人的感覺:好像是局部變量的"生命期"延長了.

那麼:核心的問題是:怎麼才能使得:訪問"複製品"與訪問真正的原始的局部變量,其語義效果是一樣的呢?
當變量是final時,若是基本數據類型,由於其值不變,因而:其複製品與原始的量是一樣.語義效果相同.(若:不是final,就無法保證:複製品與原始變量保持一致了,因爲:在方法中改的是原始變量,而局部內部類中改的是複製品)

當變量是final時,若是引用類型,由於其引用值不變(即:永遠指向同一個對象),因而:其複製品與原始的引用變量一樣,永遠指向同一個對象(由於是final,從而保證:只能指向這個對象,再不能指向其它對象),達到:局部內部類中訪問的複製品與方法代碼中訪問的原始對象,永遠都是同一個即:語義效果是一樣的.否則:當方法中改原始變量,而局部內部類中改複製品時,就無法保證:複製品與原始變量保持一致了(因此:它們原本就應該是同一個變量.)

一句話:這個規定是一種無可奈何.也說明:程序設計語言的設計是受到實現技術的限制的.這就是一例. 因爲:我就看到不少人都持這種觀點:設計與想法是最重要的,實現的技術是無關緊要的,只要你作出設計與規定,都能實現.

現在我們來看,如果我要實現一個在一個方法中匿名調用ABSClass的例子:
 public static void test(final String s){
     //或final String s = "axman";
  ABSClass c = new ABSClass(){
   public void m(){
      int x = s.hashCode();
      System.out.println(x);
   }
  };
  //其它代碼.
 }

 從代碼上看,在一個方法內部定義的內部類的方法訪問外部方法內局部變量或方法參數,是非常自然的事,但內部類編譯的時候如何獲取這個變量,因爲內部類除了它的生命週期是在方法內部,其它的方面它就是一個普通類。那麼它外面的那個局部變量或方法參數怎麼被內部類訪問?編譯器在實現時實際上是這樣的:

  public static void test(final String s){
     //或final String s = "axman";
  class OuterClass$1 extends ABSClass{
   private final String s;
   public OuterClass$1(String s){
      this.s = s;   
   }
   public void m(){
      int x = s.hashCode();
      System.out.println(x);
   }
  };
  ABSClass c = new OuterClass$1(s);
  //其它代碼.
 }

即外部類的變量被作爲構造方法的參數傳給了內部類的私有成員.
假如沒有final,那麼:
 public static void test(String s){
     //或String s = "axman";
  ABSClass c = new ABSClass(){
   public void m(){
     s = "other";
   }
  };
  System.out.println(s);
 }
 就會編譯成:
  public static void test(String s){
     //或String s = "axman";


  class OuterClass$1 extends ABSClass{
   private String s;
   public OuterClass$1(String s){
      this.s = s;   
   }
   public void m(){
     s = "other";
   }
  };

   ABSClass c = new OuterClass$1 (s);
  }

 內部類的s重新指向"other"並不影響test的參數或外部定義的那個s.同理如果外部的s重新賦值內部類的s也不會跟着改變。
 而你看到的
  public static void test(String s){
     //或String s = "axman";
  ABSClass c = new ABSClass(){
   public void m(){
     s = "other";
   }
  };
  System.out.println(s);
 }

 在語法上是一個s,在內部類中被改變了,但結果打印的出來的你認爲是同一的s卻還是原來的"axman",
 你能接收這樣的結果嗎?
 所以final從語法上約束了實際上兩個不同變量的一致性(表現爲同一變量).

轉:http://blog.csdn.net/onisland/article/details/5807637

爲什麼匿名內部類和局部內部類只能訪問final變量
是變量的作用域的問題,因爲匿名內部類是出現在一個方法的內部的,如果它要訪問這個方法的參數或者方法中定義的變量,則這些參數和變量必須被修飾爲final。因爲雖然匿名內部類在方法的內部,但實際編譯的時候,內部類編譯成Outer.Inner,這說明內部類所處的位置和外部類中的方法處在同一個等級上,外部類中的方法中的變量或參數只是方法的局部變量,這些變量或參數的作用域只在這個方法內部有效。因爲編譯的時候內部類和方法在同一級別上,所以方法中的變量或參數只有爲final,內部類纔可以引用。
Java代碼: 
package com.cxz.j2se;   
  
public class MyClass {   
    public MyClass() {   
        final int finalValue = 10;   
        int not$Final = 20;   
        MyInterface myInterface = new MyInterface() {   
            public void functionWithoutPara() {   
                //compile Error   
                //System.out.println(noFinal);    
                System.out.println(finalValue);   
            }   
  
            public void functionWithPara(int num) {   
                System.out.println("The parameter " + num   
                        + " has been passed by the method");   
            }   
  
        };   
        myInterface.functionWithoutPara();   
        myInterface.functionWithPara(not$Final);   
        System.out.println(myInterface.getClass().getName());   
    }   
  
    public static void main(String[] args) {   
        new MyClass();   
  
    }   
  


二、爲什麼局部內部類只能訪問final變量
簡單的來說是作用域的問題。就好像方法外面做的事情並不能改變方法內才定義的變量,因爲你並不知道方法裏面這個時候已經存在了這個局部變量了沒有。在這個內部類中方法裏面的本地變量是失效的,也就是不在作用域內,所以是不能夠訪問的

但是爲什麼這裏用final卻又可以訪問呢? 
因爲Java採用了一種copy   local   variable的方式來實現,也就是說把定義爲final的局部變量拷貝過來用,而引用的也可以拿過來用,只是不能重新賦值。從而造成了可以access   local   variable的假象,而這個時候由於不能重新賦值,所以一般不會造成不可預料的事情發生

三、如果定義一個局部內部類,並且局部內部類使用了一個在其外部定義的對象,爲什麼編譯器會要求其參數引用是final呢?
注意:局部內部類,包括匿名內部類。

原因如下:

abstract class ABSClass{
public abstract void print();
}

public class Test2{
public static void test(final String s){//一旦參數在匿名類內部使用,則必須是final
ABSClass c=new ABSClass(){
public void print(){
System.out.println(s);
}
};
c.print();
}
public static void main(String[] args){
test("Hello World!");
}
}

JVM中每個進程都會有多個根,每個static變量,方法參數,局部變量,當然這都是指引用類型.基礎類型是不能作爲根的,根其實就是一個存儲地址.垃圾回收器在工作時先從根開始遍歷它引用的對象並標記它們,如此遞歸到最末梢,所有根都遍歷後,沒有被標記到的對象說明沒有被引用,那麼就是可以被回收的對象(有些對象有finalized方法,雖然沒有引用,但JVM中有一個專門的隊列引用它們直到finalized方法被執行後才從該隊列中移除成爲真正沒有引用的對象,可以回收,這個與本主題討論的無關,包括代的劃分等以後再說明).這看起來很好.

但是在內部類的回調方法中,s既不可能是靜態變量,也不是方法中的臨時變量,也不是方法參數,它不可能作爲根,在內部類中也沒有變量引用它,它的根在內部類外部的那個方法中,如果這時外面變量s重指向其它對象,則回調方法中的這個對象s就失去了引用,可能被回收,而由於內部類回調方法大多數在其它線程中執行,可能還要在回收後還會繼續訪問它.這將是什麼結果?

而使用final修飾符不僅會保持對象的引用不會改變,而且編譯器還會持續維護這個對象在回調方法中的生命週期.所以這纔是final變量和final參數的根本意義.
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章