1. 程式人生 > >Java™ 教程(巢狀類)

Java™ 教程(巢狀類)

巢狀類

Java程式語言允許你在另一個類中定義類,這樣的類稱為巢狀類,如下所示:

class OuterClass {
    ...
    class NestedClass {
        ...
    }
}
術語:巢狀類分為兩類:靜態和非靜態,宣告為static的巢狀類稱為靜態巢狀類,非靜態巢狀類稱為內部類。
class OuterClass {
    ...
    static class StaticNestedClass {
        ...
    }
    class InnerClass {
        ...
    }
}

巢狀類是其封閉類的成員,非靜態巢狀類(內部類)可以訪問封閉類的其他成員,即使它們被宣告為private

,靜態巢狀類無權訪問封閉類的其他成員。作為OuterClass的成員,可以將巢狀類宣告為privatepublicprotected或包私有(回想一下,外部類只能宣告為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例項變數。

你可以使用內部類來實現輔助類,例如本示例中所示的類,要處理使用者介面事件,你必須知道如何使用內部類,因為事件處理機制會廣泛使用它們。

區域性和匿名類

還有兩種型別的內部類,你可以在方法體內宣告內部類,這些類稱為區域性類,你還可以在方法體內宣告內部類,而無需命名該類,這些類稱為匿名類。

修飾符

你可以對用於外部類的其他成員的內部類使用相同的修飾符,例如,你可以使用訪問修飾符privatepublicprotected來限制對內部類的訪問,就像你使用它們來限制對其他類成員的訪問一樣。