1. 程式人生 > >為什麼匿名內部類引數必須為final型別

為什麼匿名內部類引數必須為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從語法上約束了實際上兩個不同變數的一致性(表現為同一變數).