1. 程式人生 > >Java基礎教程(14)--巢狀類

Java基礎教程(14)--巢狀類

  Java允許在一個類中定義另外一個類,這樣的類被稱為巢狀類,就像下面這樣:

class OuterClass {
    ...
    class NestedClass {
        ...
    }
}

  巢狀類分為兩種:靜態的和非靜態的。宣告為static的巢狀類被稱為靜態巢狀類,非靜態巢狀類則被稱為內部類:

class OuterClass {
    ...
    static class StaticNestedClass {
        ...
    }
    class InnerClass {
        ...
    }
}

  巢狀類是其所在的外部類的成員。內部類可以訪問外部類中的其他成員,即使這個成員被private修飾。靜態巢狀類則沒有訪問外部類中其他成員的許可權。作為外部類的一個成員,巢狀類可以被宣告為private、public、protected或者包私有的。
  下面是幾個為什麼要使用巢狀的類原因:

  • 能夠將僅在一個地方使用的類合理地組合。如果一個類可能只對於另外一個類有用,此時將前者組合到後者,可以使得程式包更加簡潔。
  • 增強封裝性。假如有兩個類A和B,B類需要使用A類中的成員,而恰好該成員又是僅類內部可見(private)的,如果將B定義為A的巢狀類,則B可以使用A的任何成員,而且B也可以宣告為外部不可見(private),將B隱藏起來。
  • 能夠使程式碼可讀性和維護性更強。巢狀的類程式碼相較於頂級類,更靠近它被使用的地方,方便檢視。

一.靜態巢狀類

  就像靜態方法和靜態變數一樣,靜態巢狀類是和外部類相關聯的。和靜態方法一樣,靜態巢狀類不能直接引用例項變數和實力方法,只能通過一個物件引用。實際上,可以將靜態巢狀類看作是一個頂級類,只不過將其巢狀在其他類中方便打包。
  靜態巢狀類可以用過外部類的名字去訪問:

OuterClass.StaticNestedClass

  可以使用下面的語法為靜態巢狀類建立物件:

OuterClass.StaticNestedClass nestedObject = new OuterClass.StaticNestedClass();

二.內部類

  就像例項方法和例項變數一樣,內部類與外部類的例項相關聯並且可以直接訪問外部類的方法和成員。並且,因為內部類與外部類的例項相關聯,因此它內部不能定義靜態成員。
  要例項化內部類,必須建立外部類的物件,然後使用這個物件去建立內部類的物件:

OuterClass outerObject = new OuterClass();
OuterClass.InnerClass innerObject = outerObject.new InnerClass();

  實際上,還有兩種特殊的內部類————區域性類和匿名類。有關這兩種類的內容將會在下文中介紹。強烈建議不要對內部類(包括區域性類和匿名類)進行序列化(把物件轉換為位元組序列的過程稱為物件的序列化,有關序列化的內容會在以後的文章中進行介紹)。

三.遮蔽現象

  如果一個型別(例如成員變數或引數)與外部作用域中的型別同名,那麼內部作用域中的宣告將會遮蔽外部作用域中的宣告,這樣就不能直接通過名稱去訪問外部作用域中同名的型別。如下例所示:

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的引數。方法methodInFirstLevel的引數x遮蔽了內部類FirstLevel的成員變數x和ShadowTest類的成員變數x。因此,在表示內部類FirstLevel的成員變數x時,要像下面這樣:

System.out.println("this.x = " + this.x);

  在表示ShadowTest類的成員變數x時,要像下面這樣:

System.out.println("ShadowTest.this.x = " + ShadowTest.this.x);

四.區域性類

  區域性類是在塊(由大括號包圍的零條或多條語句)中定義的類。經常會在方法體中見到區域性類。下面是一個定義在方法中的區域性類:

public class OuterClass {
    public void method() {
        ...
        class LocalClass {
            ...
        }
    }
}

  區域性類可以訪問外部類的成員。此外,區域性類還可以訪問區域性變數。然而,區域性類只能訪問由final修飾的區域性變數。當一個區域性類訪問一個塊中的區域性變數或引數時,它就捕獲了這個變數或者引數。從Java8開始,區域性類不但可以訪問由final修飾的區域性變數和引數,還可以訪問近似final的區域性變數和引數。近似final的意思是說這個變數或引數的值自從初始化之後就沒有修改過。此外,區域性類中的變數也會遮蔽定義在外部的同名變數或引數。
  區域性類中基本不能定義靜態成員。不過也有例外,可以在區域性類中定義靜態常變數(常變數是指型別為基本資料型別或者String,被宣告為final,並且使用編譯時常量表達式進行初始化的變數。編譯時常量表達式通常是可以在編譯時計算的字串或算術表示式)。
  定義在靜態方法中的區域性類,只能引用外部類的靜態成員。不能在塊中定義介面,因為介面是天生靜態的。也不能在區域性類中定義靜態初始化器或者介面。

五.匿名類

  匿名類讓程式碼看上去更加簡潔,它能讓你同時宣告和例項化一個類。它們類似於區域性類,只不過匿名類沒有名稱。如果某個區域性類只使用一次,可以將它定義為匿名類。
  區域性類是類的宣告,而匿名類則是表示式,這意味著匿名類是在一個表示式中定義的。匿名類表示式的語法就像是呼叫構造器的語法,只不過構造器後面跟的是類的定義。就像下面這樣:

HelloWorldInterface frenchGreeting = new HelloWorldInterface() {
    String name = "tout le monde";
    public void greet() {
        greetSomeone("tout le monde");
    }
    public void greetSomeone(String someone) {
        name = someone;
        System.out.println("Salut " + name);
    }
};

  匿名類的語法包含以下幾部分:

  • new操作符;
  • 匿名類實現的介面或者繼承的類;
  • 包含了構造器引數的小括號。注意,當匿名類實現了某個介面時,由於介面沒有構造器,因此使用一個空的小括號來表示;
  • 匿名類的定義體。

  就像區域性類一樣,匿名類也可以捕獲變數。下面是幾條規則:

  • 匿名類可以訪問它的外部類的成員;
  • 匿名類不能訪問外部範圍中沒有使用final修飾或不是近似final的區域性變數。
  • 匿名類中會遮蔽外部範圍中同名的型別。

  匿名類在成員的定義上和區域性類有相同的規則:

  • 不能在匿名類中宣告靜態初始化器或成員介面;
  • 匿名類中可以有靜態成員,前提是這個靜態成員必須是常變數。

  可以在匿名類中宣告以下元素:

  • 域;
  • 額外的方法(介面中沒有定義的方法);
  • 非靜態初始化塊;
  • 區域性類。

  不能在匿名類中定義構造方法。