Java™ 教程(巢狀類)
巢狀類
Java程式語言允許你在另一個類中定義類,這樣的類稱為巢狀類,如下所示:
class OuterClass {
...
class NestedClass {
...
}
}
術語:巢狀類分為兩類:靜態和非靜態,宣告為static
的巢狀類稱為靜態巢狀類,非靜態巢狀類稱為內部類。
class OuterClass {
...
static class StaticNestedClass {
...
}
class InnerClass {
...
}
}
巢狀類是其封閉類的成員,非靜態巢狀類(內部類)可以訪問封閉類的其他成員,即使它們被宣告為private
OuterClass
的成員,可以將巢狀類宣告為private
、public
、protected
或包私有(回想一下,外部類只能宣告為public
或包私有)。
為什麼使用巢狀類?
使用巢狀類的令人信服的理由包括:
- 這是一種邏輯分組僅在一個地方使用的類的方法:如果一個類只對另一個類有用,那麼將它嵌入該類並將兩者保持在一起是合乎邏輯的,巢狀這樣的“幫助類”使得它們的包更加簡化。
-
它增加了封裝:考慮兩個頂級類A和B,其中B需要訪問A的成員,否則這些成員將被宣告為
private
,通過將類B隱藏在類A中,可以將A的成員宣告為私有,並且B可以訪問它們,另外,B本身也可以不被外界發現。 - 它可以使程式碼更具可讀性和可維護性:在頂級類中巢狀小類會使程式碼更接近於使用它的位置。
靜態巢狀類
與類方法和變數一樣,靜態巢狀類與其外部類相關聯,和靜態類方法一樣,靜態巢狀類不能直接引用其封閉類中定義的例項變數或方法:它只能通過物件引用來使用它們。
注意:靜態巢狀類與其外部類(和其他類)的例項成員互動,就像任何其他頂級類一樣,實際上,靜態巢狀類在行為上是一個頂級類,它已巢狀在另一個頂級類中以方便打包。
使用封閉的類名訪問靜態巢狀類:
OuterClass.StaticNestedClass
例如,要為靜態巢狀類建立物件,請使用以下語法:
OuterClass.StaticNestedClass nestedObject = new OuterClass.StaticNestedClass();
內部類
與例項方法和變數一樣,內部類與其封閉類的例項相關聯,並且可以直接訪問該物件的方法和欄位,此外,由於內部類與例項相關聯,因此本身無法定義任何靜態成員。
作為內部類的例項的物件存在於外部類的例項中,考慮以下類:
class OuterClass {
...
class InnerClass {
...
}
}
InnerClass
的例項只能存在於OuterClass
的例項中,並且可以直接訪問其封閉例項的方法和欄位。
要例項化內部類,必須首先例項化外部類,然後,使用以下語法在外部物件中建立內部物件:
OuterClass.InnerClass innerObject = outerObject.new InnerClass();
有兩種特殊的內部類:區域性類和匿名類。
遮蔽
如果特定範圍(例如內部類或方法定義)中的型別宣告(例如成員變數或引數名稱)與封閉範圍中的另一個宣告具有相同的名稱,然後宣告會影響封閉範圍的宣告,你不能僅通過其名稱引用遮蔽的宣告,以下示例ShadowTest
演示了這一點:
public class ShadowTest {
public int x = 0;
class FirstLevel {
public int x = 1;
void methodInFirstLevel(int x) {
System.out.println("x = " + x);
System.out.println("this.x = " + this.x);
System.out.println("ShadowTest.this.x = " + ShadowTest.this.x);
}
}
public static void main(String... args) {
ShadowTest st = new ShadowTest();
ShadowTest.FirstLevel fl = st.new FirstLevel();
fl.methodInFirstLevel(23);
}
}
以下是此示例的輸出:
x = 23
this.x = 1
ShadowTest.this.x = 0
此示例定義了三個名為x
的變數:ShadowTest
類的成員變數、內部類FirstLevel
的成員變數、以及方法methodInFirstLevel
中的引數。變數x
定義為方法methodInFirstLevel
的引數遮蔽內部類FirstLevel
的變數,因此,當你在方法methodInFirstLevel
中使用變數x
時,它引用方法引數,要引用內部類FirstLevel
的成員變數,請使用關鍵字this
來表示封閉範圍:
System.out.println("this.x = " + this.x);
引用成員變數,這些成員變數根據所屬的類名包含更大的作用域,例如,以下語句從方法methodInFirstLevel
訪問類ShadowTest
的成員變數:
System.out.println("ShadowTest.this.x = " + ShadowTest.this.x);
序列化
強烈建議不要對內部類(包括區域性類和匿名類)進行序列化,當Java編譯器編譯某些構造(如內部類)時,它會建立合成構造,這些是類、方法、欄位和其他在原始碼中沒有相應構造的構造。合成構造使Java編譯器能夠在不更改JVM的情況下實現新的Java語言功能,但是,合成構造可以在不同的Java編譯器實現之間變化,這意味著.class
檔案也可以在不同的實現之間變化。因此,如果序列化內部類,然後使用其他JRE實現反序列化,則可能存在相容性問題,有關在編譯內部類時生成的合成構造的更多資訊,請參閱獲取方法引數名稱一節中的隱式和合成引數部分。
內部類示例
要看內部類的使用,首先要考慮一個數組,在下面的示例中,你將建立一個數組,用整數值填充它,然後僅按升序輸出陣列的偶數索引值。
下面的DataStructure.java
示例包括:
-
DataStructure
外部類,它包含一個建構函式,用於建立DataStructure
的例項,該例項包含一個數組,該陣列填充了連續的整數值(0、1、2、3等等),以及一個方法,該方法列印具有偶數索引值的陣列元素。 -
EvenIterator
內部類,它實現了DataStructureIterator
介面,擴充套件了Iterator< Integer>介面,迭代器用於逐步執行資料結構,通常有方法來測試最後一個元素,檢索當前元素,然後移動到下一個元素。 - 例項化
DataStructure
物件(ds
)的main
方法,然後呼叫printEven
方法來列印具有偶數索引值的陣列arrayOfInts
的元素。
public class DataStructure {
// Create an array
private final static int SIZE = 15;
private int[] arrayOfInts = new int[SIZE];
public DataStructure() {
// fill the array with ascending integer values
for (int i = 0; i < SIZE; i++) {
arrayOfInts[i] = i;
}
}
public void printEven() {
// Print out values of even indices of the array
DataStructureIterator iterator = this.new EvenIterator();
while (iterator.hasNext()) {
System.out.print(iterator.next() + " ");
}
System.out.println();
}
interface DataStructureIterator extends java.util.Iterator<Integer> { }
// Inner class implements the DataStructureIterator interface,
// which extends the Iterator<Integer> interface
private class EvenIterator implements DataStructureIterator {
// Start stepping through the array from the beginning
private int nextIndex = 0;
public boolean hasNext() {
// Check if the current element is the last in the array
return (nextIndex <= SIZE - 1);
}
public Integer next() {
// Record a value of an even index of the array
Integer retValue = Integer.valueOf(arrayOfInts[nextIndex]);
// Get the next even element
nextIndex += 2;
return retValue;
}
}
public static void main(String s[]) {
// Fill the array with integer values and print out only
// values of even indices
DataStructure ds = new DataStructure();
ds.printEven();
}
}
輸出是:
0 2 4 6 8 10 12 14
請注意,EvenIterator
類直接引用DataStructure
物件的arrayOfInts
例項變數。
你可以使用內部類來實現輔助類,例如本示例中所示的類,要處理使用者介面事件,你必須知道如何使用內部類,因為事件處理機制會廣泛使用它們。
區域性和匿名類
還有兩種型別的內部類,你可以在方法體內宣告內部類,這些類稱為區域性類,你還可以在方法體內宣告內部類,而無需命名該類,這些類稱為匿名類。
修飾符
你可以對用於外部類的其他成員的內部類使用相同的修飾符,例如,你可以使用訪問修飾符private
、public
和protected
來限制對內部類的訪問,就像你使用它們來限制對其他類成員的訪問一樣。