【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"); } };
一個匿名類由以下幾個部分組成:
- new操作符
- Runnable:介面名稱。這裡還可以填寫抽象類、普通類的名稱。
- ():這個括號表示建構函式的引數列表。由於Runnable是一個介面,沒有建構函式,所以這裡填一個空的括號表示沒有引數。
- {...}:大括號中間的程式碼表示這個類內部的一些結構。在這裡可以定義變數名稱、方法。跟普通的類一樣。
訪問許可權
那麼匿名內部類能訪問哪些東西呢?按照規則,可以訪問如下內容:
- 訪問外層Class裡面的欄位。
- 不能訪問外層方法中的本地變數。除非變數是final。
- 如果內部類的名稱和外面能訪問的名稱相同,則會把名稱覆蓋掉。
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() { }
};
}
}
因為匿名類沒有名字,而建構函式需要把類名作為方法名才能看成建構函式。 匿名類中可以包含的東西有:
- 欄位
- 方法
- 例項初始化程式碼
- 本地類
為什麼不能定義靜態初始化程式碼
事實上,內部類中不能定義任何靜態的東西。
關鍵字: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為了保持清晰的語法,不允許這種有歧義的語法存在。