.new 語法Java 允許在類別內部定義類別(內部類別),並可多層巢狀。每一層內部類別實例都隱含持有外部類別實例的參考,形成一條「外部實例鏈」。本文透過實際程式碼解析多層巢狀結構下,內部類別如何存取外部類別的成員、實例化順序、以及 .new 語法的作用域語意。
public class AccessMethod { // 頂層類別 (Outer)
private void f() { System.out.println("f()"); }
class A1 { // 非靜態內部類別 (Middle 1)
private void g() { System.out.println("A1 - g()"); }
}
class A { // 非靜態內部類別 (Middle 2)
private void g() { System.out.println("g()"); }
class B { // 非靜態內部類別 (Inner)
void h() {
System.out.println("h()");
g(); // 呼叫 A.g()
f(); // 呼叫 AccessMethod.f()
}
}
}
public static void main(String[] args) {
AccessMethod am = new AccessMethod();
AccessMethod.A a = am.new A(); // 必須透過外部實例建立
AccessMethod.A.B b = a.new B(); // 必須透過中層實例建立
b.h();
}
}
h()
g()
f()
關鍵觀察:
B.h() 直接呼叫 g() 與 f(),無需加前綴。編譯器依作用域鏈向外搜尋:先找 B、再找 A、最後找 AccessMethod。A.g() 遮蔽了 AccessMethod.A1.g()(雖然名稱相同,但不同類別)。AccessMethod → A → B。.new 語法的作用域語意外部實例.new 內部類別() 是 Java 特有語法,編譯器將其轉換為隱含建構子參數:
// 原始碼
AccessMethod.A a = am.new A();
// 編譯器實際產生 (概念上)
AccessMethod.A a = new AccessMethod.A(am); // 傳入外部實例參考
這確保了內部類別實例永遠繫結於特定的外部實例,獲得存取外部非靜態成員的能力。
無法直接呼叫。 原因:B 實例持有的外部鏈是 AccessMethod → A → B,不包含 A1 實例。若要呼叫 A1.g(),必須先有 A1 實例,但 B 的建構過程未建立 A1。
若 A1.g() 宣告為 static,則可用 A1.g() 或 AccessMethod.A1.g() 呼叫。但 Java 語言規定:非靜態內部類別 (如 A1) 內不能宣告 static 方法 (JLS §8.1.3)。只有「頂層類別」或「靜態內部類別」才能定義 static 成員。
public class Test {
static void t() {} // 頂層類別可定義 static
class T2 {
// static void a() {} // 編譯錯誤:非靜態內部類別不可定義 static 方法
}
static class T3 { // 靜態內部類別
static void a() {} // 合法
}
}
若需在深層巢狀類別中共享邏輯,建議:
AccessMethod) 或靜態內部類別A1 改為 static class A1,使其獨立於外部實例,可定義 static 方法| 內部類別類型 | 能否定義 static 成員 | 是否持有外部實例參考 | 實例化語法 |
|---|---|---|---|
| 非靜態內部類別 (Member Inner Class) | 否 (Java 16+ 允許 static final 常數) | 是 | outer.new Inner() |
| 靜態內部類別 (Static Nested Class) | 是 | 否 | new Outer.StaticInner() |
| 區域內部類別 (Local Class) | 否 | 是 (捕獲 final/效果上 final 區域變數) | 在方法/區塊內 new LocalClass() |
| 匿名內部類別 | 否 | 是 | new Interface/Class() { ... } |
static nested class 表達「邏輯歸屬」而非「實例綁定」的關係Outer$Inner.class,反編譯可見隱含的 Outer this$0 欄位