Java SE 6 技術手冊

9.1 內部類別

在類別中您還可以再定義類別,稱之為「內部類別」(Inner class)或「巢狀類別」(Nested class)。非靜態的內部類別可以分為三種:「成員內部類別」(Member inner class)、「區域內部類別」(Local inner class)與「匿名內部類別」(Anonymous inner class)。內部類別的主要目的,都是對外部隱藏類別的存在性。

9.1.1 成員內部類別、區域內部類別

使用內部類別有幾個好處,其一是內部類別可以直接存取其所在類別中的私用(private)成員;其二是當某個 Slav e類別完全只服務於一個 Master 類別時,您可以將之設定為內部類別,如此使用 Master 類別的人就不用知道 Slave 的存在;再者是像在「靜態工廠」(Static factory)模式中,對呼叫靜態方法的物件隱藏返回物件的實作細節或產生方式。

先來介紹成員內部類別,基本上是在一個類別中直接宣告另一個類別,例如:

public class OuterClass { 
    // 內部類別 
    private class InnerClass { 
        // .... 
    } 
}

成員內部類別同樣也可以使用 "public"、"protected" 或 "private" 來修飾其存取權限,範例 9.1 簡單示範成員內部類別的使用。

範例 9.1 PointDemo.java

public class PointDemo { 
    // 內部類別 
    private class Point { 
        private int x, y; 

        public Point() { 
        } 

        public void setPoint(int x, int y) { 
            this.x = x; 
            this.y = y; 
        } 

        public int getX() { return x; } 
        public int getY() { return y; } 
    } 

    private Point[] points; 

    public PointDemo(int length) { 
        points = new Point[length]; 

        for(int i = 0; i < points.length; i++) { 
            points[i] = new Point(); 
            points[i].setPoint(i*5, i*5); 
        } 
    } 

    public void showPoints() { 
        for(int i = 0; i < points.length; i++) { 
            System.out.printf("Point[%d]: x = %d, y = %d%n",
                      i, points[i].getX(), points[i].getY());
        } 
    } 
}

程式中假設 Point 類別只服務於 PointDemo 類別,外界不必知道 Point 類別的存在,只要知道如何操作 PointDemo 的實例就可以了,就像範例 9.2 所示。

範例 9.2 PointShow.java

public class PointShow { 
    public static void main(String[] args) { 
        PointDemo demo = new PointDemo(5); 

        demo.showPoints(); 
    } 
}

執行結果:

Point[0]: x = 0, y = 0
Point[1]: x = 5, y = 5
Point[2]: x = 10, y = 10
Point[3]: x = 15, y = 15
Point[4]: x = 20, y = 20

在檔案管理方面,成員內部類別在編譯完成之後,所產生的檔案名稱為「外部類別名稱$內部類別名稱.class」,所以範例 9.1 在編譯過後會產生兩個檔案:PointDemo.class 與 PointDemo$Point.class。

區域內部類別的使用與成員內部類別類似,區域內部類別定義於一個方法中,類別的可視範圍與生成之物件僅止於該方法之中。

內部類別還可以被宣告為 "static",不過由於是 "static",它不能存取外部類別的方法,而必須透過外部類別所生成的物件來進行呼叫,被宣告為 static的內部類別,事實上也可以看作是另一種名稱空間的管理方式,例如:

public class Outer {
    public static class Inner {
        ....
    }
    ....
}

您可以如以下的方式來使用 Inner 類別:

Outer.Inner inner = new Outer.Inner();

良葛格的話匣子 有關內部類別的實際應用,建議看一下「靜態工廠模式」,對於使用靜態工廠類別的物件,它不需要知道返回物件的實例是如何產生的,只需要知道如何操作返回的物件,所以您可以宣告一個介面,並在靜態工廠中定義一個實作該介面的內部類別,由該內部類別產生實例,這是內部類別的一個應用場合。

另外也可以看一下「Iterator 模式」,一個實例是您在 Java SE 的 API 文件中找不到實作 java.util.Iterator 介面的類別,因為實作 Iterator 介面的類別是定義在集合類別(像是 java.util.ArrayList)之中,您只需要知道如何操作 Iterator 介面就可以了。

9.1.2 匿名內部類別

內部匿名類別可以不宣告類別名稱,而使用 "new" 直接產生一個物件,內部匿名類別可以是繼承某個類別或是實作某個介面,內部匿名類別的宣告方式如下:

new [類別或介面()] { 
// 實作 
}

一個使用內部匿名類別的例子如範例7.3所示,您直接繼承 Object 類別定義一個匿名類別,重新定義 toString() 方法,並使用匿名類別直接產生一個物件。

範例 9.3 AnonymousClassDemo.java

public class AnonymousClassDemo { 
    public static void main(String[] args) { 
        Object obj = 
            new Object() { 
                public String toString() { // 重新定義toString()
                    return "匿名類別物件"; 
                } 
            }; 

        System.out.println(obj); 
    } 
}

使用 System.out.println() 時如果傳入的是物件,會呼叫物件的 toString() 方法得到 String 實例,所以範例的輸出如下所示:

匿名類別物件

注意如果要在內部匿名類別中使用外部的區域變數,變數在宣告時必須為 "final",例如下面的陳述是無法通過編譯的:

....
    public void someMethod() {
        int x = 10; // 沒有宣告final
        Object obj = 
            new Object() { 
                public String toString() {
                    return String.valueOf(x); // x不可在匿名類別中使用
                } 
            }; 
        System.out.println(obj); 
    }
....

在進行編譯時,編譯器會回報以下的錯誤:

local variable x is accessed from within inner class; needs to be declared final

您要在宣告區域變數x時加上"final"才可以通過編譯:

....
    public void someMethod() {
        final int x = 10; // 宣告final
        Object obj = 
            new Object() { 
                public String toString() {
                    return String.valueOf(x); // x可在匿名類別中使用
                } 
            }; 
        System.out.println(obj); 
    }
....

為什麼要加上 "final" 宣告呢?原因在於區域變數 x 並不是真正被拿來於內部匿名類別中使用,x 會被匿名類別複製作為資料成員來使用,由於真正在匿名類別中的x是複製品,即使您在內部匿名類別中對 x 作了修改(例如 x=10 的指定),也不會影響真正的區域變數x,事實上您也通不過編譯器的檢查,因為編譯器會要求您加上 "final" 關鍵字,這樣您就知道不能在內部匿名類別中改變 x 的值,因為即使能改變也沒有意義。

在檔案管理方面,內部匿名類別在編譯完成之後會產生「外部類別名稱$編號.class」,編號為 1、2、3...n,每個編號 n 的檔案對應於第 n 個匿名類別,所以範例 9.3編 譯完成後,會產生 AnonymousClassDemo.class與AnonymousClassDemo$1.class 兩個檔案。