1. 靜態巢狀類別
靜態巢狀類別(static nested class)是指在另一個類別內使用修飾詞 static 宣告的類別。本質上,它是一般的類別,只是「住在」另一個類別裡,且不與其物件綁定。
如果把內部類別比作總是牽著外部類別(外部類別物件)之手的弟弟,那麼靜態巢狀類別就像只在家庭聚會出現、其他時間各自生活的表親。
關鍵差異:
- 沒有指向外部類別物件的隱式參照——沒有 OuterClass.this,也不能存取非靜態成員。
- 可以包含靜態成員(不同於一般內部類別)。
- 建立時不需要外部類別的物件。
宣告語法
宣告靜態巢狀類別很簡單:在外部類別中使用關鍵字 static。
class Outer {
static class Nested {
void print() {
System.out.println("Hello from Nested!");
}
}
}
就這樣!沒有任何花俏的語法——就是 static class。
視覺化:
Outer(外部類別)
│
├── Nested(static nested class)
│ └── print()
2. 建立靜態巢狀類別的實例
最棒的是:不需要外部類別的物件!
Outer.Nested nested = new Outer.Nested();
nested.print(); // Hello from Nested!
請注意:我們使用類別的全名——Outer.Nested。這就像用姓氏來稱呼巢狀類別:「Smith.Son」。
與內部(inner)類別對比:
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner(); // 需要 outer 物件
而對於 static nested——完全不需要 Outer 物件!
3. 存取外部類別的成員
這裡就是與內部類別最大的不同。
- 靜態巢狀類別只能存取外部類別的靜態成員。
- 無法存取非靜態欄位與方法(即便它們是 public)。
範例:
class Outer {
private static int staticValue = 10;
private int instanceValue = 20;
static class Nested {
void show() {
System.out.println("Static value: " + staticValue); // OK
// System.out.println("Instance value: " + instanceValue); // 錯誤!
}
}
}
如果嘗試存取非靜態欄位 instanceValue,編譯器立刻會對你「善意提醒」。
為什麼?因為 static nested class 並不知道要和哪個 Outer 物件關聯——它沒有指向外部類別物件的參照。
4. 何時使用靜態巢狀類別
什麼時候適合?
- 當巢狀類別在邏輯上與外部類別相關,但不需要存取外部類別的物件。
- 當想封裝輔助性結構:例如 builder、工具類、列舉,或者小型不可變物件。
- 當需要減少套件的「雜訊」:類別只供外部類別使用,沒有必要搬到外面。
典型情境:
- Builder 模式(特別是不可變物件)
- 實作輔助性結構:例如集合中的內部 Node 類別
- 彙整常數或工具類
簡單的選擇法則
問自己一個問題:「我的巢狀類別是否需要存取外部類別的特定物件?」
否 → 使用 static class(靜態巢狀)
是 → 使用一般 class(內部類別)
5. 使用範例
範例 1:類別的 Builder
假設我們有一個 Person 類別,並想為它實作 Builder 模式:
public class Person {
private final String name;
private final int age;
// 私有建構子
private Person(Builder builder) {
this.name = builder.name;
this.age = builder.age;
}
// 靜態巢狀類別 Builder
public static class Builder {
private String name;
private int age;
public Builder setName(String name) {
this.name = name;
return this;
}
public Builder setAge(int age) {
this.age = age;
return this;
}
public Person build() {
return new Person(this);
}
}
public void printInfo() {
System.out.println("Person: " + name + ", " + age);
}
}
使用方式:
Person person = new Person.Builder()
.setName("Ivan")
.setAge(30)
.build();
person.printInfo(); // Person: Ivan, 30
為什麼 Builder 要做成靜態巢狀類別?
因為它不依賴 Person 的物件,它只是幫助建立該物件。它在邏輯上與 Person 有關,但不是與特定的實例綁定。
範例 2:集合內的輔助結構
想像一個「裝著數字的盒子」類別,使用內部的靜態類別 Node 來存放元素:
public class IntBox {
private Node head;
// 巢狀 static class
private static class Node {
int value;
Node next;
Node(int value) {
this.value = value;
}
}
public void add(int value) {
Node node = new Node(value);
node.next = head;
head = node;
}
public void printAll() {
Node current = head;
while (current != null) {
System.out.println(current.value);
current = current.next;
}
}
}
使用方式:
IntBox box = new IntBox();
box.add(1);
box.add(2);
box.add(3);
box.printAll(); // 3 2 1
為什麼 Node 要是 static?
因為每個 Node 不需要知道整個盒子(IntBox),它只保存資料與指向下一個 Node 的參考。
範例 3:主要類別中的工具類
public class MathUtils {
// 用於複數運算的靜態巢狀類別
public static class Complex {
private final double re;
private final double im;
public Complex(double re, double im) {
this.re = re;
this.im = im;
}
public Complex add(Complex other) {
return new Complex(this.re + other.re, this.im + other.im);
}
@Override
public String toString() {
return re + " + " + im + "i";
}
}
}
使用方式:
MathUtils.Complex a = new MathUtils.Complex(1, 2);
MathUtils.Complex b = new MathUtils.Complex(3, 4);
MathUtils.Complex sum = a.add(b);
System.out.println(sum); // 4.0 + 6.0i
6. 實用細節
內部類別 vs 靜態巢狀類別
| 內部(inner)類別 | 靜態巢狀(static nested) | |
|---|---|---|
| 關鍵字 | 無 | |
| 隱式參照外部物件 | 有 | 無 |
| 可存取外部類別的非靜態成員 | 是 | 否 |
| 可存取外部類別的靜態成員 | 是 | 是 |
| 可包含靜態成員 | 否(僅常數) | 是 |
| 建立語法 | |
|
| 適用情境 | 當需要存取外部類別的物件 | 當不需要存取外部類別的物件 |
示意圖
flowchart LR
OuterClass -->|has| InnerClass
OuterClass -.->|has| StaticNestedClass
StaticNestedClass -.->|can access| staticMembers
InnerClass -->|can access| instanceMembers
InnerClass -->|can access| staticMembers
特性與限制
- Static nested class 可以同時包含一般與靜態的欄位與方法。
- 可以使用任意存取修飾詞宣告(public、private、protected、package-private)。
- 可以實作介面並繼承其他類別。
- 可以是 generic。
- 通常用來封裝僅於外部類別內部需要的輔助類別。
generic 靜態巢狀類別範例:
public class Box {
public static class Holder<T> {
private T value;
public Holder(T value) { this.value = value; }
public T get() { return value; }
}
}
何時不該使用 static nested class
- 如果巢狀類別需要存取外部類別的非靜態欄位/方法——請使用一般的 inner class。
- 如果該類別會在外部類別之外使用——把它放到獨立檔案。
- 如果類別過於龐大或複雜——最好做成獨立類別。
7. 常見錯誤與細節
錯誤 1:混淆 inner 與 static nested 類別。
許多新手會在 static nested class 中嘗試存取外部類別的非靜態欄位。但 static nested class 沒有外部類別物件的參照,因此做不到。若你需要存取某個具體物件的狀態——請使用一般的 inner class。
錯誤 2:嘗試透過外部類別的物件來建立 static nested class。
沒有必要寫 outer.new Inner()。對於 static nested class,請一律使用 new Outer.Nested()。
錯誤 3:在需要存取外部類別實例的邏輯中使用 static nested class。
若類別的邏輯與外部類別物件的狀態緊密相連,static nested class 不是好選擇。請使用一般的內部類別。
錯誤 4:過度複雜的巢狀結構。
別濫用巢狀類別。若結構開始變得混亂,最好把部分類別移到外面。
GO TO FULL VERSION