淺談java內部類

帥地發表於2019-03-04

一、什麼樣的類屬於內部類?

毫無疑問,居然是內部類,定義在類裡面的類就是內部類。

public class Room {  
    class Student{//內部類   
    }  
}
複製程式碼

內部類可以分為靜態內部類(static修飾的類)和普通內部類。下面先討論普通內部類:

(注意,以下所說的內部類都是指普通內部類)

1.內部類與外部類的關係
對於普通內部類,實際上該類就類似於外部類的一個特殊成員,該內部類的建立必須依附於外部類,也就是說,必須先有外部類物件,才能建立內部類物件。這是因為內部類物件會隱式地連線到建立它的外部類物件上。並且內部類可以訪問外部類的所有元素。建立時通過 .new來建立

public class Room {  
    private String book = "資料結構";  
    class Student{//內部類  
        public String getBook(){  
            return book;  
        }  
    }  
    public static void main(String[] args) {  
        Room r = new Room();  
        Student s = r.new Student();//不能有 Student s = new Student()來建立  
        System.out.println(s.getBook());  
    }  
} 
複製程式碼

注:如果要在內部類通過this關鍵字來引用外部類物件,則需要在這樣引用 Room.this 。如果只是用this的話,引用的是內部類物件。

2.在方法和作用域內的內部類

(1).定義在方法內的內部類,我們稱之為區域性內部類:

public void get(){//只貼方法內的程式碼,其他程式碼省略  
    class Book{ //定義在方法內的內部類  
        String name = book;//book變數是外部類的成員屬性  
    }  
}
複製程式碼

顯然,Book內只是方法get()的一部分,而不是外部類的一部分,所以,get()方法之外都不能訪問Book。

(2).下面展示任何在任意作用域內嵌入一個內部類

public void get2(boolean b){  
    if(b){//在if語句作用域內定義內部類  
        class Book2{  
            String name = book;  
        }  
    }  
} 
複製程式碼

(3).內部類不能定義靜態變數和靜態方法

原因:類在初始化的過程中,是先初始化靜態變數等的。也就是說,在編譯是隻要是定義為靜態變數了,系統就會自動分配記憶體給它,但是內部類是依附與外部類的存在而存在的,也就是說只有外部類存在了,內部類才能存在,這就和編譯時為靜態變數分配記憶體產生了衝突。

3.匿名內部類

看下面一個程式

public class Animal {  
    public void run(){  
        System.out.println("我是一隻不知名的動物");  
    }  
    public Animal getAnimal(){  
        return new Animal(){//返回匿名內部類  
            public void run(){  
                System.out.println("我是一隻小貓");  
            }  
        };  
    }  
    public static void main(String[] args) {  
        Animal a = new Animal();  
        Animal a1 = a.getAnimal();  
        a1.run();  
    }  
}
複製程式碼

列印結果:我是一隻小貓

在getAnimal()方法裡,將返回值的生成與表示這個返回值的類定義結合在一起。另外,這個類是匿名的,沒有名字。對於這種用法,看起來似乎你正要建立一個Animal的物件,實際上,建立的卻是一個繼承了Animal類的匿名物件,run()方法 是對父類方法的重寫。

對於這種匿名內部類,使用的是預設構造器來生成Animal的,如果你的基類(也就是父類)需要一個有引數的構造器,該怎麼辦:
只需要把引數傳遞給基類的構造器即可。

public Animal getAnimal2(String name){  
    return new Animal(name){//直接把引數給它就行  
        public void run(){  
            System.out.println("我是一隻" + name);  
        }  
    };  
}
複製程式碼

以下來討論靜態內部類:

對於靜態內部類,它與普通內部類不同。普通的內部類物件會隱式地儲存了一個引用,指向建立它的外部類物件。然而,對於靜態類來說,就不是這樣了:

(1).要建立靜態內部類,它並不需要其外部類的物件

public class Book {   
    static class Math{  }  
    public static void main(String[] args) {  
        Math m = new Math();//靜態內部類的建立  
    }  
}
複製程式碼

(2).靜態內部類不能訪問非靜態的外部類物件

public class Book {  
    int B_price = 10;  
    static String B_name = "好書";  
      
    static class Math{  
        //int M_price = B_price;//不能訪問  
        String M_name = B_name;  
    }  
}
複製程式碼

對於為何不能訪問非靜態變數我是這樣理解的:非靜態量是屬於類物件成員,而不是類成員,它的存在是依附於物件而不是類。靜態內部類訪問這個外部類成員的時候,這個成員還不存在,因為外部物件還沒有被建立。故不能訪問非靜態成員。(當然,也可以從類載入的順序等分析)。

(3).靜態內部類還可以作為介面的一部分。

正常情況下,是不能在介面內部放置任何程式碼的,但是靜態內部類可以作為介面的一部分。你放到介面中的任何類都自動地是public和static的。因為類是static的,只是將靜態內部類置於介面的名稱空間內,這並不會違反介面的規則:

public interface AA {  
    void get();  
    class BB implements AA{  
        @Override  
        public void get() {  
            System.out.println("哈哈");     
        }     
    }  
} 
複製程式碼

二、為啥需要內部類?

1.對於抽象類,我們都知道只能被單繼承,如果一個類想要繼承多個抽象類,那隻能使用內部類才能實現多重繼承(即定義幾個內部類,每個內部類分擔繼承一個)。

2.內部類的使用,對一個類來說,提供了更好的封裝。例如要生產一輛車,我們可以假設老闆是外部類,負責組裝車部件的團隊是內部類。那麼你想要修改車裡面的部件時,不僅僅是通過老闆就可以直接做出修改了,還得通過負責裝車部件的團隊。也就是說,外部類的有些屬性成員,只要通過內部類才能修改,這樣子設計,會更具合理性。

3.內部類可以有多個例項,每個例項都有自己的狀態資訊,並且與其外部類物件的資訊相互獨立。

4.在單個外部類中,可以讓多個內部類以不同的方式實現同一個介面,或繼承同一個類。

三、內部類可以被覆蓋嗎?

看下面一個例子

列印結果:
Eat
Eat.Apple
複製程式碼

從輸出的情況可以看出,內部類並沒有被覆蓋。也就是說,兩個內部類並沒有啥關係,是相互獨立的。

參考書籍:程式設計思想

來源與公眾號:苦逼的碼農
原文連線

淺談java內部類

相關文章