1. 程式人生 > >【Android】Java 匿名類初探~~

【Android】Java 匿名類初探~~

在很多工程中可以看到一個操作,Java可以直接new一個介面,然後在new裡面粗暴的加入實現程式碼。就像下面這樣。

思考以下程式碼的輸出是什麼?

Runnable x = new Runnable() {  
    @Override  
    public void run() {  
        System.out.println(this.getClass());  
    }  
};  
x.run();

實際答案是出現xxxx$1這樣一個類名,它是編譯器給定的名稱。

那麼問題來了,new出來的物件沒有實際的類作為載體,這不是很奇怪嗎?於是就引出了匿名類這個概念。

匿名類

匿名類相當於在定義類的同時再新建這個類的例項。我們來看看匿名類的編譯結果。

這個類的程式碼如下:

public class Test {  
  public void test() {  
    Runnable r = new Runnable(){  
      @Override  
      public void run(){  
        System.out.println("hello");  
      }  
    };  
  }  
}

來看看它的編譯結果,通過javap反向編譯Test.class,得到的結果如下:

SourceFile: "Test.java"  
EnclosingMethod: #20.#21                // Test.test  
InnerClasses:  
     #6; //class Test$1

發現了一個欄位叫EnclosingMethod,說明這個類是定義在Test.test方法下的。那現在有個問題,如果有兩個test方法,會出現什麼呢?

原來是旁邊這個註釋不太準確,實際上會包含函式簽名的。請看Constant Pool部分,這裡的#21指向了這個函式簽名。

#21 = NameAndType        #34:#16        // test:()V

匿名類的語法

這裡舉一個簡單的例子:

Runnable hello = new Runnable() {  
    public void run() {  
        System.out.println("hello");  
    }  
};

一個匿名類由以下幾個部分組成:

  1. new操作符
  2. Runnable:介面名稱。這裡還可以填寫抽象類、普通類的名稱。
  3. ():這個括號表示建構函式的引數列表。由於Runnable是一個介面,沒有建構函式,所以這裡填一個空的括號表示沒有引數。
  4. {...}:大括號中間的程式碼表示這個類內部的一些結構。在這裡可以定義變數名稱、方法。跟普通的類一樣。

訪問許可權

那麼匿名內部類能訪問哪些東西呢?按照規則,可以訪問如下內容:

  1. 訪問外層Class裡面的欄位。
  2. 不能訪問外層方法中的本地變數。除非變數是final。
  3. 如果內部類的名稱和外面能訪問的名稱相同,則會把名稱覆蓋掉。
public class A {  
    private int foo;  
    public void test() {  
        Runnable r = new Runnable() {  
            System.out.println(foo);  
        };  
    }  
}

匿名類裡面不可以有的東西: 1.不能定義靜態初始化程式碼塊(Static Initializer)。比如下面的程式碼是不符合語法的:

public class A {  
    public void test() {  
        Runnable r = new Runnable() {  
            static { System.out.println("hello"); }  
        };  
    }  
}

2.不能在匿名類裡面定義介面。

比如:

public class A {  
    public void test() {  
        Runnable r = new Runnable() {  
            public interface Hello { };  
        };  
    }  
}

和上面一樣,也是為了語義的清晰。interface只能定義靜態的。

3.不能在匿名類中定義建構函式。

public class A {  
    public void test() {  
        Runnable r = new Runnable() {  
            public Runnable() { }  
        };  
    }  
}

因為匿名類沒有名字,而建構函式需要把類名作為方法名才能看成建構函式。 匿名類中可以包含的東西有:

  1. 欄位
  2. 方法
  3. 例項初始化程式碼
  4. 本地類

為什麼不能定義靜態初始化程式碼

事實上,內部類中不能定義任何靜態的東西。

關鍵字:Inner class cannot have static declarations

StackOverFlow上看起來有一種解釋如下。

首先來看一個內部類。

public class A {  
  public class B {  
  }  
}

它編譯之後,會變成下面這種含義:

public class A {  
  public static class B {  
    private final A parent;  
    public B(A parent) {  
      this.parent = parent;  
    }  
  }  
}

所以,按照這麼說,內部類就是一種語法糖。當我們定義靜態變數時,就會產生下面這種歧義。下面的程式碼看起來沒什麼問題。

public class A {  
  private int a;  
  public class B {  
    public static void test() {  
      a = 1;  
    }  
  }  
}

但是編譯之後,問題就來了。

public class A {  
  private int a;  
  public static class B {  
    private final A parent;  
    public B (A parent) { this.parent = parent; }  
    public static void test() {  
      parent.a = 1; // 這裡有語法錯誤  
    }  
  }  
}

所以,歸根結底,Java為了保持清晰的語法,不允許這種有歧義的語法存在。