CodeGym /課程 /JAVA 25 SELF /內部類別(non-static inner)

內部類別(non-static inner)

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

1. 什麼是內部類別(inner class)?

在 Java 中,類別不僅可以在最外層(檔案層級)宣告,也可以在另一個類別內宣告。這樣的類別稱為巢狀(nested class)。若該類別在宣告時未使用 static 修飾子,則稱為內部(non-static inner class,或簡稱 inner class)。

內部類別是宣告在另一個類別之內,並且與該外部類別實例關聯的類別。它可以存取外部類別的所有欄位與方法,即使它們是 private。就好像物件有一位「祕密助手」,被允許做任何事!

可以把它想成房子與房間。房子是外部類別,房間是內部類別。房間不能離開房子獨立存在,但能使用房子的資源:照明、暖氣、家具。若房子消失,房間也會消失。Java 的內部類別正是如此運作!

為什麼需要內部類別?

  • 邏輯關聯:當某個類別只用來配合另一個類別、在其之外沒有意義時。
  • 封裝:能隱藏實作細節,不會汙染套件的命名空間。
  • 存取私有成員:內部類別可以存取外部類別的 private 欄位與方法。
  • 精簡:減少套件層級的「雜項」類別數量。

內部類別的宣告語法

宣告內部類別很簡單:在另一個類別的主體內宣告,且不使用 static 修飾子。

class Outer {
    // 外部類別的欄位與方法

    class Inner {
        // 內部類別的欄位與方法
        void printHello() {
            System.out.println("Hello from Inner!");
        }
    }
}

這裡的 Inner 相對於 Outer 是內部類別。

示意圖:視覺化

Outer
│
├─ 欄位/方法
│
└─ Inner(內部類別)
      └─ 自己的欄位/方法

2. 如何建立內部類別的實例

這裡藏著新手最常踩的坑之一!內部類別的實例永遠與某個外部類別物件綁定。

範例:

Outer outer = new Outer();             // 建立外部類別的物件
Outer.Inner inner = outer.new Inner(); // 透過外部物件建立內部類別!
inner.printHello();                    // Hello from Inner!

若只寫 new Inner(),會導致編譯錯誤,因為 Java 不知道要把這個內部類別綁定到哪一個外部類別物件。

為什麼?

因為內部類別可以存取外部類別的欄位與方法,所以它必須「知道」自己是與哪一個外部類別物件一起工作的。

3. 內部類別的使用範例

我們來看一個內部類別真正有用的情境。

範例 1:模型「背包與物品」

假設我們有一個 Backpack 類別,可以存放物品(Item)。但我們希望 Item 只在 Backpack 內可用,因為離開背包我們不關心物品。

public class Backpack {
    private String owner;

    public Backpack(String owner) {
        this.owner = owner;
    }

    // 內部類別——物品只能存在於背包之中!
    class Item {
        private String name;

        public Item(String name) {
            this.name = name;
        }

        public void printInfo() {
            // 魔法!看得到外部類別的 private 欄位
            System.out.println(owner + " 擁有物品:" + name);
        }
    }
}

使用方式:

Backpack bp = new Backpack("瓦夏");
Backpack.Item item = bp.new Item("Java 教科書");
item.printInfo(); // 瓦夏 擁有物品:Java 教科書

請注意:Item 即使在欄位 ownerprivate 的情況下也能存取!

範例 2:自訂集合的迭代器

在 Java 中,集合常以內部類別實作迭代器。我們來做一個簡單的集合,並用內部類別實作迭代器。

public class IntList {
    private int[] data = new int[10];
    private int size = 0;

    public void add(int value) {
        data[size++] = value;
    }

    // 內部類別——迭代器!
    class Iterator {
        private int index = 0;

        public boolean hasNext() {
            return index < size;
        }

        public int next() {
            return data[index++];
        }
    }
}

使用方式:

IntList list = new IntList();
list.add(10);
list.add(20);

IntList.Iterator it = list.new Iterator();
while (it.hasNext()) {
    System.out.println(it.next());
}

4. 內部類別的特性

存取外部類別的成員

內部類別可以存取任何(即使是 private)外部類別的欄位與方法。這很方便,但也需要小心:若外部類別改變,內部類別可能會突然「壞掉」。

class Outer {
    private int secret = 42;

    class Inner {
        void showSecret() {
            System.out.println("祕密:" + secret);
        }
    }
}

內部類別不能包含靜態成員

內部(non-static inner)類別不能包含靜態欄位或方法,唯有靜態的 final 常數例外(例如,public static final int MY_CONST = 123;)。原因是內部類別總是「繫結」到外部類別的實例。

如果你需要宣告靜態巢狀類別——請使用 static 修飾子(下一堂課會介紹)。

內部類別的可見性

內部類別可以是 privateprotectedpublic,或具有套件層級(package-private)的可見性。

public class Outer {
    private class Inner { /* ... */ }
}

5. 實用細節

何時應使用內部類別

內部類別不是為了好看,而是為了邏輯性地組織程式碼。使用它們於下列情況:

  • 類別只在一處需要(例如輔助用的迭代器、處理器、複雜結構的一部分)。
  • 類別與外部類別高度耦合,離開它便沒有意義。
  • 希望將實作細節隱藏於其他套件內的類別。

不要為了趨勢而使用內部類別! 有時若該類別在其他地方也可能有用,把它提到最外層會更好。

內部類別與 this

在內部類別中,可以使用 OuterClassName.this 存取外部類別的欄位與方法。

class Car {
    private String model = "Tesla";

    class Engine {
        void printModel() {
            // 明確地參照外部物件
            System.out.println("型號:" + Car.this.model);
        }
    }
}

當內部與外部類別有同名欄位時,這種語法特別有用。

什麼時候不該使用內部類別

不建議使用於以下情況:

  • 內部類別不會存取外部類別的欄位/方法
  • 該類別可能在程式的其他地方也有用
  • 外部類別變得過於龐大且複雜

在這些情況下更好的做法是:

  • 將類別改為靜態巢狀類別(static class)
  • 把類別拆到獨立檔案
  • 以一般方法取代整個類別

6. 使用內部類別時的常見錯誤

錯誤一:嘗試在沒有外部物件的情況下建立內部類別。
如果寫 new Inner(),編譯器會拋出錯誤:"No enclosing instance of type Outer is accessible"。務必透過外部類別的物件來建立內部類別:outer.new Inner()

錯誤二:嘗試在內部類別中宣告靜態欄位或方法。
內部類別不能包含靜態成員(常數除外)。若有需要——請使用靜態巢狀類別,也就是用 static 修飾子來宣告類別。

錯誤三:過度使用內部類別。
如果內部類別不使用外部類別的欄位/方法,它很可能應該是 static,或乾脆拉到最外層。不要「以防萬一」就建立內部類別。

錯誤四:混淆對外部類別欄位的參照。
當內部與外部類別有同名欄位時,請使用 OuterClassName.this.field 明確地參照外部類別的欄位。

1
任務
JAVA 25 SELF, 等級 16, 課堂 0
上鎖
倉儲管理:箱子標籤 📦
倉儲管理:箱子標籤 📦
1
任務
JAVA 25 SELF, 等級 16, 課堂 0
上鎖
個人助理:禮貌的問候 🗣️
個人助理:禮貌的問候 🗣️
1
任務
JAVA 25 SELF, 等級 16, 課堂 0
上鎖
魔法圖書館:卷軸編目 📜
魔法圖書館:卷軸編目 📜
1
任務
JAVA 25 SELF, 等級 16, 課堂 0
上鎖
智慧家庭:房間位址 🏠
智慧家庭:房間位址 🏠
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION