CodeGym /課程 /JAVA 25 SELF /靜態巢狀類別(static nested)

靜態巢狀類別(static nested)

JAVA 25 SELF
等級 16 , 課堂 1
開放

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)
關鍵字
static
隱式參照外部物件
可存取外部類別的非靜態成員
可存取外部類別的靜態成員
可包含靜態成員 否(僅常數)
建立語法
outer.new Inner()
new Outer.Nested()
適用情境 當需要存取外部類別的物件 當不需要存取外部類別的物件

示意圖

flowchart LR
    OuterClass -->|has| InnerClass
    OuterClass -.->|has| StaticNestedClass
    StaticNestedClass -.->|can access| staticMembers
    InnerClass -->|can access| instanceMembers
    InnerClass -->|can access| staticMembers

特性與限制

  • Static nested class 可以同時包含一般與靜態的欄位與方法。
  • 可以使用任意存取修飾詞宣告(publicprivateprotected、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:過度複雜的巢狀結構。
別濫用巢狀類別。若結構開始變得混亂,最好把部分類別移到外面。

留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION