1. 程式人生 > >Java的內部類及其建立方式

Java的內部類及其建立方式

內部類及其建立方式

引言

在定義類時,我們可以將一個類定義在另一個類的內部,此時這個類便被成為內部類。

內部類是一種非常有用的特性,因為它允許你吧一些邏輯相關的類組織在一起,並控制位於內部的類的可視性。然而必須要了解,內部類與組合是兩種完全不同地方概念,這一點很重要。

在一開始,內部類看起來可能想hi是一種程式碼的隱藏機制,僅僅是用來將類的定義放置在其他的類的內部,但是,隨著對於內部類的不斷了解,我們會發現其遠不止如此,它能夠了解定義它的外部類,並能與之通訊,利用內部類的種種特點,我們可以將程式碼變得更加優雅和清晰。

基本的內部類的建立方式

正如上面對於內部類的定義一樣,當把一個類的定義置於另一個類的內部時,我們便完成了一個內部類的建立。

在下面,我們假設建立了一個披薩店的類,在其中定義了兩個內部類,分別為乳酪披薩cheesePizza與香腸披薩sausagePizza。

package learning_java.InnerClassTest;

public class PizzaStore {
    class cheesePizza {
        private String name = "cheesePizza";
        public void getName() {System.out.println("Got a " + name);}
    }
    class sausagePizza
{ private String name = "sausagePizza"; public void getName() {System.out.println("Got a " +name);} } // 只有在該外部類的非靜態方法中,才可以這樣直接建立內部類的物件 public void orderPizza(String pizzaName) { if (pizzaName == "cheesePizza") { cheesePizza pizza = new cheesePizza(); pizza.
getName(); } else if (pizzaName == "sausagePizza"){ sausagePizza pizza = new sausagePizza(); pizza.getName(); } else System.out.println("Don't have this kind of pizza"); } public static void main(String[] args) { PizzaStore store = new PizzaStore(); store.orderPizza("cheesePizza"); } }

在定義了兩個披薩的內部類之後,我們又定義了一個訂披薩的方法orderPizza,在這個方法裡,我們會根據所輸入的需要來建立披薩的物件。而我們在這個方法裡面使用內部類的時候,看起來似乎與普通類並沒有什麼不同,在這裡,實際的區別只是內部類的定義時巢狀在PizzaStore類裡面的,但是接下來我們會看到,這並不是唯一的區別。

public class PizzaStore2 {
    class cheesePizza {
        private String name = "cheesePizza";
        int size;
        cheesePizza(int inputSize){
            size = inputSize;
        }
        public void getName() {System.out.println("Got a " + name + "\tof size:\t" + size);}
    }
    class sausagePizza {
        private String name = "sausagePizza";
        int size;
        sausagePizza(int inputSize){
            size = inputSize;
        }
        public void getName() {System.out.println("Got a " + name + "\tof size:\t" + size);}
    }

    public sausagePizza getSausagePizza(int size){
        return new sausagePizza(size);
    }
    public cheesePizza getCheesePizza(int size){
        return new cheesePizza(size);
    }
    public void orderPizza(String pizzaName) {
        if (pizzaName == "cheesePizza") {
            cheesePizza pizza = new cheesePizza(5);
            pizza.getName();
        }
        else if (pizzaName == "sausagePizza"){
            sausagePizza pizza = new sausagePizza(5);
            pizza.getName();
        }
        else
            System.out.println("Don't have this kind of pizza");
    }
    // 如果想從外部類的非靜態方法之外的任意位置建立某個內部類的物件,那麼必須像在main()方法中這樣,具體地知名這個物件的型別:OuterClassName.InnerClassName
    public static void main(String[] args) {
        PizzaStore2 store = new PizzaStore2();
        store.orderPizza("cheesePizza");

        PizzaStore2 store2 = new PizzaStore2();
        PizzaStore2.cheesePizza pizza = store2.getCheesePizza(6);
        pizza.getName();
    }
}

在上面的程式碼中,我們為兩個披薩寫了其建構函式,並用getCheesePizzagetSausagePizza方法來返回這兩個披薩的例項,而我們在main()中宣告其的時候用的是PizzaStore2.cheesePizza。與這個定義類似,對於內部類,如果想從外部類的非靜態方法之外的任意位置建立某個內部類的物件,那麼必須像在main()方法中這樣,具體地知名這個物件的型別:OuterClassName.InnerClassName。即,即使定義該外部類的內部類的靜態方法也不行,除該外部類之外的所有類都不可以。

舉個例子,如果在我們的披薩商店pizzaStore2中加入如下方法,可以看到其建立例項的方式與之前orderPizza方法是完全相同的。

public static void test() {
        cheesePizza pizza = new cheesePizza(5);
}

此時便會報錯:

Error:(29, 29) java: 無法從靜態上下文中引用非靜態 變數 this

內部類與外部類的連結

到目前位置,內部類似乎還只是一種組織程式碼的模式,但是我們馬上就能看到它的別的用途。當我們生成一個內部類的物件的同時,此物件與製造它的外圍物件(enclosing object)之間就有了一個一種聯絡,這種聯絡使得它能夠訪問其外圍物件的所有成員,而不需要任何特殊條件。此外,不僅於外圍物件,內部類還擁有其外圍類的所有元素的訪問權。

public class PizzaStore3 {
    private int materialsNum;
    private static int storeNum = 0;
    private static int allPizzaNum = 0;
    public void showMaterialsNum() {System.out.println("the num of materials:\t" + materialsNum);}
    public static void showStoreNum() {System.out.println("the num of store:\t" + storeNum);}
    public static void showAllPizzaNum() {System.out.println("the num of all the pizza:\t" + allPizzaNum);}
    public PizzaStore3(int materialsNum) {
        this.materialsNum = materialsNum;
        storeNum ++;
    }
    class cheesePizza implements Pizza{
        private String name = "cheesePizza";
        int size;
        cheesePizza(int inputSize){
            size = inputSize;
            materialsNum -= size;
            allPizzaNum ++;
        }
        public void getName() {System.out.println("Got a " + name + "\tof size:\t" + size);}
    }
    class sausagePizza implements Pizza{
        private String name = "sausagePizza";
        int size;
        sausagePizza(int inputSize){
            size = inputSize;
            materialsNum -= size;
            allPizzaNum ++;
        }
        public void getName() {System.out.println("Got a " + name + "\tof size:\t" + size);}
    }

    public sausagePizza getSausagePizza(int size){
        return new sausagePizza(size);
    }
    public cheesePizza getCheesePizza(int size){
        return new cheesePizza(size);
    }

    public void orderPizza(String pizzaName) {
        Pizza pizza;
        switch(pizzaName) {
            case "cheesePizza": pizza = new cheesePizza(5);break;
            case "sausagePizza": pizza = new sausagePizza(5);   break;
            default:    System.out.println("Don't have this kind of pizza"); return;
        }
        pizza.getName();
    }
    public static void main(String[] args) {
        PizzaStore3 store = new PizzaStore3(100);

        store.showMaterialsNum();
        PizzaStore3.showAllPizzaNum();

        PizzaStore3.cheesePizza pizza = store.getCheesePizza(6);
        pizza.getName();

        store.showMaterialsNum();
        PizzaStore3.showAllPizzaNum();
    }
}

在這裡,我們依然以我們的披薩店為例,這一次,我們給每個披薩店增加了一個int型別的成員materialsNum來記錄店裡的原料的數量,而我們每製作一個披薩就會消耗與其大小相同數量的原料,同時為我們的PizzaStore3類增加了一個靜態變數allPizzaNum來記錄我們所有的披薩店所製作的披薩的數量,每當我們呼叫披薩的建構函式製作出一張披薩時,我們就在這個全域性的數量上加一。

之後,我們在main()中新建了一個store,並在store中製作了一個pizza,我們將以上的資訊打印出來:

the num of materials:	100
the num of all the pizza:	0
Got a cheesePizza	of size:	6
the num of materials:	94
the num of all the pizza:	1

可以看到,無論對於物件的成員,還是類的靜態成員,內部類都可以與其進行房訪問並進行修改。當我們仔細檢視內部類cheesePizzasausagePizza時,其建構函式都用到了變數materials,而這個變數並不是屬於這兩個內部類的,而是屬於外圍類pizzaStoreprivate欄位,這裡其實是一個引用,這使得內部類可以訪問外圍類的方法和欄位,就像自己擁有它們似的,這帶來了很大的方便。

所有內部類自動擁有對其外圍類所有成員的訪問權,但是這是如何做到的呢?其實,當某個外圍類的物件建立了一個內部類的物件時,此內部類物件必定會祕密地捕獲一個指向那個外圍類物件的引用,然後,在訪問詞外圍類的成員時,就是用那個引用來選擇外圍類的成員。而這一系列過程中,編譯器會幫助我們處理所有的細節,但現在可以看到,內部類的物件只能在與其外圍類的物件的關聯的情況下才能被建立(當內部類不是static時),構建一個內部類的物件時,需要一個指向其外圍類的物件的引用,如果編譯器訪問不到這個引用,就會報錯。而內部類與其外圍類的這種關係,便引出了我們的下一節的內容。

通過.this來在內部類中引用其外圍類

如上一節中所講,一個外部類中的內部類的物件,是與這個外部類的物件存在著一個連結的依存關係的,那麼我們是不是可以在內部類中顯式地調起這個外部類的物件呢?答案是肯定的。如果我們在內部類中生成對於外圍類物件的引用,可以使用外部類的名字後面緊跟.this,這樣產生的引用自動對地具有正確的型別,這一點在編譯期就會被知曉並受到檢查,因此沒有任何執行時的開銷。我們在以下這個簡化的披薩商店的類中來展示這一點。

public class PizzaStore4 {
    void getStoreName() {System.out.println("PizzaStore4!");}
    public class CheesePizza {
        public PizzaStore4 getStore() {
            // 在這裡通過.this來引用其外圍類
            return PizzaStore4.this;
        }
    }
    public CheesePizza cheesePizza() {return new CheesePizza();}

    public static void main(String[] args) {
        PizzaStore4 store = new PizzaStore4();
        PizzaStore4.CheesePizza pizza = store.cheesePizza();
        pizza.getStore().getStoreName();
    }
}

輸出:

PizzaStore4!

通過.new來建立內部類的物件

在第一節中,當我們建立內部類的物件時,用的都是外部類中提供的可以返回一個新的內部類的物件的方法,如getCheesePizzagetSausagePizza,而外部類中並不總是提供這樣的方法,那麼我們怎樣直接得到一個內部類的物件呢?

正如內部類與外部類的物件的連結一節中所講,每個內部類都是與其外部類的一個物件相連結的,因此在建立內部類的物件時,也需要體現出這一點,我們是不能夠直接new一個內部類的物件出來的,那如何與外部類的物件產生關聯呢?這裡,我們就需要使用.new語法了,下面這個例子會介紹這個語法的使用方法。

public class PizzaSotre5 {
    public class CheesePizza{}
    public static void main(String[] args) {
        PizzaSotre5 store = new PizzaSotre5();
        // 通過.new來建立內部類的物件
        PizzaSotre5.CheesePizza pizza = store.new CheesePizza();
    }
}

由於我們是直接用PizzaStore來建立的內部類物件pizza,因此這樣便直接解決了內部類物件與外部類物件的連結與內部類物件的作用域問題。實際上,這裡我們不能去引用外部類的名字PizzaStore5,而必須用物件來創捷內部類物件,我們不必也不能store.new PizzaStore5.CheesePizza()

由此,我們可以看到,在擁有外部類的物件之前,是不餓能建立內部類的物件的。這是因為內部類物件會自動地連線到建立它的外部類的物件上。但是,如果我們建立的是巢狀類(靜態內部類),那麼它就不需要對外部類物件的引用。