CodeGym /課程 /JAVA 25 SELF /匿名類別

匿名類別

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

1. 認識匿名類別

想像你有一個類別 Animal,用來描述動物的行為。這個類別有一個方法 say(),會輸出 "動物發出聲音"。你需要建立一個會像狗一樣行為並輸出 "汪!" 的物件,但你不想為此另外建立一個 Dog.java 檔案。

這時候 匿名類別 就能派上用場。它是沒有名稱的類別,會在使用處直接宣告並建立。🚀 它能讓你「就地」繼承某個類別,而不需要額外建立檔案。與其為只在單一位置使用的許多小類別新增檔案,不如在需要的地方直接把邏輯寫好。

語法長什麼樣?

匿名類別的語法看起來有點特別,但其實很簡單:

TipPeremennoi imya = new TipDlyaNasledovaniya() {
    // 在這裡撰寫匿名類別的主體
    // 覆寫父類別的方法
};

讓我們拆解一下這段語法:

  • TipPeremennoi imya:這是一般的變數宣告,用來保存我們的新物件。
  • new TipDlyaNasledovaniya():看起來像是在建立新物件。但這裡用的是我們想要繼承的類別名稱。注意,括號 () 之後沒有分號 !
  • { ... }:接著打開大括號,撰寫匿名類別的所有邏輯。我們可以覆寫父類別的方法,或加入自訂邏輯。

從一般類別繼承的範例:

回到我們的動物範例。

class Animal {
    void say() {
        System.out.println("動物發出聲音");
    }
}

// 建立匿名類別,繼承自 Animal
Animal dog = new Animal() {
    // 覆寫方法 say()
    @Override
    void say() {
        System.out.println("汪汪! 🐶");
    }
};

Animal cat = new Animal() {
    @Override
    void say() {
        System.out.println("喵喵! 🐱");
    }
};

dog.say(); // 會輸出: 汪汪! 🐶
cat.say(); // 會輸出: 喵喵! 🐱

在這個例子中,我們建立了兩個物件:dogcat。它們實際上都是從 Animal 繼承而來的匿名類別。同時我們並沒有建立任何額外檔案。

方案比較

// 一般作法(建立獨立的類別)
class Dog extends Animal {
@Override
void say() { System.out.println("汪!"); }
}
Animal dog = new Dog();

// VS

// 匿名類別(全部寫在同一處)
Animal dog = new Animal() {
@Override
void say() { System.out.println("汪!"); }
};

結果相同,但匿名類別更精簡,也不會讓專案變得雜亂!

2. 編譯後匿名類別的名稱

匿名類別在原始碼中沒有名稱,但 Java 編譯器仍需要依某種規則為其命名,以產生 .class 檔。規則如下:

  • 匿名類別檔名由宣告它的外部類別名稱開始。
  • 外部類別名稱後面會加上美元符號 $
  • 接著是此檔案中匿名類別的序號,從 1 開始。

因此,如果我們的動物範例在檔案 Main.java 中,編譯後會產生三個檔案:

  • Main.class
  • Main$1.class(對應狗的匿名類別)
  • Main$2.class(對應貓的匿名類別)

如果匿名類別宣告在一個方法裡,而該方法本身又位於內部類別中,名稱會更複雜,例如 OuterClass$InnerClass$1.class

這是編譯器的內部命名慣例,了解即可;在日常開發中很少需要直接關心。重點是——匿名類別依然是完整的類別,只是原始碼中沒有名字而已。

3. 重要特性與限制

匿名類別 是很強大的工具,但也有其規則。

存取變數。 匿名類別可以使用所在方法中的變數,但這些變數必須是 finaleffectively final(也就是初始化後其值不再變更)。

public void doSomething() {
    String greeting = "嗨!"; // 這個變數是 effectively final

    class OuterClass {
        void greet() {
            // 在方法內建立匿名類別
            new Object() {
                void sayHello() {
                    System.out.println(greeting); // 這是允許的
                    // greeting = "再見!"; // 這會導致編譯錯誤!
                }
            }.sayHello();
        }
    }

    new OuterClass().greet();
}

為什麼?因為匿名類別的生命期可能比方法本身更長;若它能改變該變數,將導致不可預期的問題。

沒有建構子。 由於匿名類別沒有名稱,因此也無法定義建構子。不過可以使用 初始化區塊 在建立物件時執行程式碼:

Animal dog = new Animal() {
    // 初始化區塊
    {
        System.out.println("匿名類別初始化 🐶");
    }
    @Override
    void say() {
        System.out.println("汪汪!");
    }
};

限制。 匿名類別不能宣告靜態欄位(常數除外)或方法。它們總是作為其他物件的一部分被建立,因此不能標記為 staticpublicprotectedprivate

4. 實用細節

何時使用匿名類別

  • 你只需要繼承某個類別(或實作介面)一次
  • 實作內容很小——1–2 個方法、不超過幾十行。
  • 類別只在單一位置需要,沒必要給它一個名字。
  • 你想避免以大量一次性的小類別使套件變得雜亂。

典型情境:

  • 事件處理器(GUI、Swing、Android 等)。
  • 將回呼(callback)傳入方法。
  • 快速實作用於集合排序的比較器。
  • 暫時性、改動過的物件(例如用於測試)。

5. 與外部類別的互動

如果匿名類別宣告在外部類別的非靜態方法或區塊內,它可以存取該外部類別的欄位與方法(包含 private!)。

public class Outer {
    private String secret = "機密文字";

    // 基底類別
    class Printer {
        public void print() {
            System.out.println("一般輸出");
        }
    }

    public void revealSecret() {
        Printer p = new Printer() {
            @Override
            public void print() {
                System.out.println("存取私有成員: " + secret);
            }
        };
        p.print();
    }

    public static void main(String[] args) {
        new Outer().revealSecret();
    }
}

6. 使用匿名類別時的常見錯誤

錯誤 1:嘗試修改所在方法的變數。
如果你在匿名類別之外宣告了一個變數,並且在匿名類別中使用後又嘗試去修改它——編譯器會報錯。該變數必須是 final 或 effectively final(初始化後不可再變更)。

錯誤 2:匿名類別過於龐大。
如果匿名類別膨脹到數十行且有多個方法——就該把它抽出為具名類別。否則程式碼會變得難以閱讀。

錯誤 3:嘗試使用靜態方法或欄位。
在匿名類別中不能宣告靜態方法或欄位(常數除外)。如果真的需要——請改用一般巢狀類別。

錯誤 4:忽略作用域。
匿名類別只在宣告處可見,且沒有名稱。若需要重複使用——請宣告一般類別。

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