在類別中您還可以再定義類別,稱之為「內部類別」(Inner class)或「巢狀類別」(Nested class)。非靜態的內部類別可以分為三種:「成員內部類別」(Member inner class)、「區域內部類別」(Local inner class)與「匿名內部類別」(Anonymous inner class)。內部類別的主要目的,都是對外部隱藏類別的存在性。
使用內部類別有幾個好處,其一是內部類別可以直接存取其所在類別中的私用(private)成員;其二是當某個 Slav e類別完全只服務於一個 Master 類別時,您可以將之設定為內部類別,如此使用 Master 類別的人就不用知道 Slave 的存在;再者是像在「靜態工廠」(Static factory)模式中,對呼叫靜態方法的物件隱藏返回物件的實作細節或產生方式。
先來介紹成員內部類別,基本上是在一個類別中直接宣告另一個類別,例如:
public class OuterClass {
// 內部類別
private class InnerClass {
// ....
}
}
成員內部類別同樣也可以使用 "public"、"protected" 或 "private" 來修飾其存取權限,範例 9.1 簡單示範成員內部類別的使用。
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 所示。
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 介面就可以了。
內部匿名類別可以不宣告類別名稱,而使用 "new" 直接產生一個物件,內部匿名類別可以是繼承某個類別或是實作某個介面,內部匿名類別的宣告方式如下:
new [類別或介面()] {
// 實作
}
一個使用內部匿名類別的例子如範例7.3所示,您直接繼承 Object 類別定義一個匿名類別,重新定義 toString() 方法,並使用匿名類別直接產生一個物件。
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 兩個檔案。