1. 程式人生 > >Java中的匿名內部類及內部類的二三事

Java中的匿名內部類及內部類的二三事

匿名內部類適合建立那些只需要使用一次的類,它的語法有些奇怪,建立匿名內部類會立即建立一個該類的例項,這個類定義立即消失,且不能重複使用。

定義匿名類的格式如下:

new 實現介面() |父類構造器(實參列表){
//匿名內部類的類體部分
}

從定義來看,匿名內部類必須繼承一個父類,或者實現一個介面,但是最多隻能繼承一個父類或者實現一個介面。

關於匿名內部類,還有如下兩條規則:

  1. 匿名內部類不能是抽象類,因為系統在建立匿名內部類時,會立即建立匿名內部類的物件。因此不允許將匿名內部類定義成抽象類。
  2. 匿名內部類不能定義構造器。因為匿名內部類沒有類名,所以無法定義構造器。但是匿名內部類可以定義初始化塊,通過例項初始化塊來完成構造器需要完成的事情。

下面給出了一個常見的匿名內部類的使用示例:

public class Main {
    public static void main(String[] args){
        Main main = new Main();
        main.test(new Man(){
            //這裡傳入一個Man介面的匿名實現類的例項
            public void talk(){
                System.out.print("I'm a man!");
                //output I'm a man!
            }
        });
    }
    
    public void test(Man m){
        //呼叫該方法需要傳入一個Man型別引數
        m.talk();
    }
}

interface Man{
    void talk();
}

定義一個匿名內部類無需使用class關鍵字,而是在定義匿名內部類時直接生成該匿名內部類的物件。

由於匿名內部類不能是抽象類,所以匿名內部類必須實現它的抽象父類或者接口裡包含的抽象方法。

雖然上面的例子完全可以採用使用實現類物件的方法來完成相同的功能,但是明顯使用匿名內部類更加簡潔。

當通過實現介面來建立匿名內部類時,匿名內部類不能顯式建立構造器,因此匿名內部類只有一個隱式的無引數構造器,故new介面名後的括號中不能傳入引數。

但是如果通過繼承父類來建立匿名內部類時,匿名內部類將擁有和父類相似的構造器,注意此處相似的構造器指的是擁有相同形參列表。

下面是通過繼承父類來建立匿名內部類的示例:

public class Main {
    public static void main(String[] args){
        Main main = new Main();
        main.test(new Man("Amos H"){
            //這裡傳入一個Man介面的匿名實現類的例項
            public void talk(){
                System.out.println(this.getName());
                //output Amos H
            }
            public int getHight(){
                return 175;
            }
        });
    }
    
    public void test(Man m){
        //呼叫該方法需要傳入一個Man型別引數
        m.talk();
    }
}

abstract class Man{
    private String name;
    public Man(String name){
        this.name = name;
    }
    public void talk(){
        System.out.println(name);
    };
    public String getName(){
        return name;
    }
    public abstract int getHight();
}

可以看到,創造匿名內部類可以使用和父類相同的引數列表,呼叫父類的構造器。

當建立匿名內部類時,必須實現介面或者抽象父類中的所有抽象方法。如果有必要的話,可以重寫父類中的普通方法。

內部類的二三事:

非靜態內部類物件和外部類物件的關係如何?

非靜態內部類物件必須寄生在外部類物件中,而外部類物件則不一定有非靜態內部類物件寄生其中。因此外部類物件不能訪問非靜態內部類物件,而非靜態內部類物件可以訪問外部類成員,因為存在內部類必然存在外部類。

非靜態內部類物件是否可以有靜態初始化塊?

非靜態內部類物件不可以有靜態初始化塊,但是可以有普通初始化塊。非靜態內部類普通初始化塊的作用與外部類初始化塊的作用完全相同。

為何靜態內部類的例項方法也不能訪問外部類的例項屬性?

因為靜態內部類是外部類的類相關的,而不是外部類的物件相關的。靜態內部類物件不是寄生在外部類的例項中,而是寄生在外部類的類本身中。當靜態內部類物件存在時,並不一定存在一個被它寄生的外部類物件,靜態內部類物件只持有外部類的類引用,並沒有持有外部類物件的引用。如果允許靜態內部類的例項方法訪問外部類的例項成員,但找不到被寄生的外部類物件,這將引起錯誤。

如何例項化一個非靜態內部類

非靜態內部類的子類不一定是內部類,它也可以是一個外部類。但是非靜態內部類的子類例項一樣要保留一個引用,該引用用於指向其父類所在外部類的物件。也就是說,如果一個內部類子類的物件存在,則一定存在與之對應的外部類物件。因為要例項化一個非靜態內部類的語法通常是這樣的:

Out.In in = new Out().new In();

可以看到,非靜態內部類的構造器必須使用外部類物件來呼叫。