Java梳理之理解內部類

今天整理一下內部類,其中包含了內部類的特殊形式,對比普通類有什么區別和作用,內部類和外圍類之間的聯系,內部類的擴展和被擴展,內部類編譯后文件的標識符。文章中有任何錯誤或遺漏請告知博主~

前置概念

嵌套類型:將一個類innerClass或者接口innerInterface的定義放在另一個類outterClass或者接口outterInterface定義的內部,那么類innerClass就是內部類、outterClass則是外圍類,類似的接口innerInterface為嵌套接口,接口outterInterface為外圍接口。

從這里可以看出嵌套類型之間的區別取決于內部類型是一個類還是一個接口,以及它的外圍類型是一個類還是一個接口。內部類型可以是靜態的,也可以不是:前者允許簡單的類型結構,后者定義了它和包圍類對象之間的特別關系。
例如:

class OutterClass{
    class InnerClass{
        private String name;
    }
    interface InnerInterface{
        String getName();
    }
    static class InnerClass2{
        String name;
    }
}
interface OutterInterface{
    class InnerClass{
        private String name;
        String getName(){
            return name;
        }
    }
    interface InnerInterface{
        void getName();
    }
}

在這篇文章中,從最常見的類中嵌套類開始理解整個嵌套類型~

內部類

知道了內部類的概念之后,可以根據類的各種形式得到一些特殊的內部類,其中包括,靜態內部類,局部內部類,匿名內部類。在對整個內部類應該有個大體的認識后,在這里拋出疑問,為什么需要內部類,僅僅是因為定義在另一個類的內部么?
其實問題在《Thinking in Java》中有早有說明:每個內部類都能獨立地繼承自一個(接口的)實現,所以無論外圍類是否已經繼承了某個(接口的)實現,對于內部類來說都沒有影響。這句話說明了它可以提供一個功能,即,使用內部類后,外圍類可以得到類似于多繼承的能力。但不僅于此,使用內部類還可以獲得其他一些特性:

1.內部類可以有多個實例,每個實例都有自己的狀態信息,并且與外圍類的對象信息相互獨立。
2.在單個外圍類中,可以用多個內部類以不同的形式實現同一個接口或繼承同一個類。
3.內部類屬于外圍類的一個輕量級可選組件。
4.內部類提供了更好的封裝效果,只能依靠外圍類來訪問。
注:《Thinking in Java》中寫的幾條特性里面只選了前面兩條,并去掉其中第三條換了第四條,如果有不同理解可以一起討論。

大體上,內部類的作用已經被明確了,但是我們并不清楚定義內部類是如何和外圍類進行聯系的,下面可以看一下內部類和外圍類的聯系機制。

內部類與外圍類的聯系

盡管我們知道內部類的作用和帶來的特性,但是并不清楚它是如何做到這些的,例如它能提供類似于多繼承的機制,但是內部類和外圍類又有什么關系?如果無法將內部類和外圍類聯系在一起,那么內部類僅僅只是寫在類定義里面的一個普通類,無法帶來它特殊的作用。如下代碼:

/**
***內部類與外圍類的聯系
**/
public class InnerClassDemo {
    public static void main(String[] arg0){
        OutterClass outter = new OutterClass();
        OutterClass.InnerClass inner = outter.new InnerClass();
        System.out.println("--inner.getName = "+inner.getName());
    }

}
class OutterClass{
    private String name = "OutterClassName";
    class InnerClass{
        public String getName(){
            return name;
        }
    }
}   
輸出:
--inner.getName = OutterClassName

可以看到,內部類本身并沒有定義name字段,它的方法是得到name字段的值,輸出的name字段的值是外圍類的name字段的值,那么外圍類和內部類之間必然是有關聯的,這種關聯就是內部類特性和作用的關鍵。在上面的例子中,內部類自然擁有了外圍類成員的訪問權限,這是如何做到的呢?其實在代碼中可以看到,內部類InnerClass實例inner的創建是通過其外部類實例outter.new出來的,那么內部類對象是有機會獲取外圍類對象的引用的。其實事實上也確實如此,在訪問外圍類成員的字段時,就是通過捕獲的外圍類對象引用(非static的內部類)這個引用是限定-this的引用(這個引用強調了一個概念:內部類對象是和包圍類對象緊密綁定在一起的,這也是同一個外圍類對象實現的一部分),通過這個引用可以訪問它的包圍類的所有成員,包括私有的。包圍類對象也可以訪問它內部類對象的私有成員,但只能先聲明字段才能訪問。

知道了內部類和外部類的聯系后,就可以靈活使用內部類語法規則來完成我們的設計,下面來看內部類的使用。

內部類的使用

創建

因為內部類提供了更好的封裝性,我們只能通過它的外圍類來訪問它,那么怎么在外圍類外部創建內部類對象呢?在沒有使用內部類的時候,我們創建一個類型的實例時,通常選擇使用new關鍵字,但是在內部類這里,會發現new關鍵字只有在外圍類內部才起作用,而在外圍類之外是無法new出來的,其實這也是和內部類與外部類的聯系有關,內部類既然要自然擁有訪問外圍類實例的權限,自然要與外圍類實例聯系在一起,所以需要如上例所示通過外圍類實例使用new創建:OutterClass.InnerClass inner = outter.new InnerClass();
當然這種特別的new的方式不是唯一解,我們也可以選擇另一種創建方式:

/**
***  .this創建
**/
public class innerClassDemo {
    public static void main(String[] arg0){
        OutterClass outter = new OutterClass();
        OutterClass.InnerClass inner = outter.getInnerClass();
        System.out.println("--inner.getName = "+inner.getName());
    }
}
class OutterClass{
    private String name = "OutterClassName";
    public InnerClass getInnerClass(){
        return new InnerClass();
    }
    class InnerClass{
        public OutterClass getOutterClass(){
            return OutterClass.this;
        }
        public String getName(){
            return name;
        }
    }
}
輸出:
--inner.getName = OutterClassName

在這個例子中,我們直接通過方法返回一個內部類,在這個內部類中使用.this來綁定它的外圍類對象,輸出的結果也是正確的。

總結一下,有兩種方式在外部創建一個類的內部類,一種是.this,另一種是.new

繼承

在之前,我們的內部類一直是一個基類。很多時候,我們使用的內部類,需要繼承一個已經存在的類,這個類中存在了一些基本的實現。既然是繼承那么內部類的繼承也是有選擇性的,它可以繼承一個普通類,也可以繼承一個其他類的內部類。在繼承普通類時,就像一般的類,但是如果它要繼承另外一個內部類,如下:

/**
***內部類繼承內部類
**/
public class innerClassDemo {
    public static void main(String[] arg0){
        OutterClass outter = new OutterClass();
        OutterClass.InnerClass inner = outter.getInnerClass();
        System.out.println("--inner.getName = "+inner.getName());
    }
}
class OutterClass extends OutterClass2{
    private String name = "OutterClassName";
    public InnerClass getInnerClass(){
        return new InnerClass();
    }
    class InnerClass extends InnerClass2{
        public OutterClass getOutterClass(){
            return OutterClass.this;
        }
        public String getName(){
            return name;
        }
    }
}
class OutterClass2{
    private String name = "OutterClassName2";
    public InnerClass2 getInnerClass(){
        return new InnerClass2();
    }
    class InnerClass2{
        public OutterClass2 getOutterClass(){
            return OutterClass2.this;
        }
        public String getName(){
            return name;
        }
    }
}
輸出:
--inner.getName = OutterClassName

在這里可以看到InnerClass繼承了InnerClass2,程序也是正確的運行的,當我把InnerClass類的外圍類OutterClass的繼承關系去掉,就會提示錯誤,這說明,如果一個內部類要繼承另外一個內部類,那么需要它的外圍類也繼承它要繼承的內部類的外圍類,即InnerClass要繼承InnerClass2,則OutterClass要繼承OutterClass2。

把內部類繼承內部類說完之后,再看一下,外部類繼承包圍類的一個內部類,例如:

/**
***外部類繼承內部類
**/
public class innerClassDemo {
    public static void main(String[] arg0){
        Unrelate unrelate = new Unrelate(new OutterClass());
        System.out.println("unrelateName = "+unrelate.getName());
    }
}
class OutterClass{
    private String name = "OutterClassName";
    public InnerClass getInnerClass(){
        return new InnerClass();
    }
    class InnerClass{
        public OutterClass getOutterClass(){
            return OutterClass.this;
        }
        public String getName(){
            return name;
        }
    }
}
class Unrelate extends OutterClass.InnerClass{
    Unrelate(OutterClass outter){
        outter.super();
    }
}
輸出:
unrelateName = OutterClassName

在這里,如果直接class Unrelate extends InnerClass會報錯,因為它的超類InnerClass是一個內部類,需要關聯一個外圍類,所以正確的寫法是class Unrelate extends OutterClass.InnerClass,盡管Unrelate類不是一個內部類,也不是一個外圍類,但是還是需要給它傳入一個外圍類對象綁定。而Unrelate對象的使用和其他普通類并沒有什么不同。

作用字段,繼承和隱藏

在這里整體的分析一下內部類的作用情況。
首先,內部類獲取了它外圍類的引用,所以外圍類的所有字段和方法都是可以使用的,術語叫作 在作用字段內。但是,內部類自己也存在的字段和方法,如果內部類的字段和方法和外圍類的字段方法名一樣,會不會造成沖突?代碼如下:

/**
***內部類作用字段
**/
public class innerClassDemo {
    public static void main(String[] arg0){
        OutterClass outter = new OutterClass();
        OutterClass.InnerClass inner = outter.new InnerClass();
        System.out.println("--getName = "+inner.getName());
    }
}
class OutterClass{
    private String name = "OutterClassName";
    public InnerClass getInnerClass(){
        return new InnerClass();
    }
    public String getName(){
        System.out.println("--OutterClass-getName-");
        return name;
    }
    class InnerClass{
        private String name = "InnerClassName";
        public OutterClass getOutterClass(){
            return OutterClass.this;
        }
        public String getName(){
            System.out.println("--InnerClass-getName-");
            return name;
        }
    }
}
輸出:
--InnerClass-getName-
--getName = InnerClassName

這里可以看到,輸出的結果和之前在InnerClass中沒有name字段時不一樣,inner使用了它自己的字段name,而屬于外圍類的字段name和方法getName()被隱藏了,其實隱藏外圍類的情況共有兩種:
1.內部類有自己的字段和方法。
2.內部類的父類有字段和方法。

在這兩種情況下,任意的簡單名用法,都會直接引用內部類成員。因為會出現隱藏的問題,所以在內部類內使用外圍類的字段和方法的時候,建議使用.this來限定,例如:OutterClass.this.name

把一般的內部類說完之后,我們看一下幾種特別的內部類:

靜態內部類

定義:把一個靜態類定義在另一個類定義中,就是靜態內部類(嵌套類),和普通的內部類相比,定義時使用了static關鍵字。
還記得之前,普通的內部類對象會在內部捕獲并保存它外圍類對象的引用,但是,靜態內部類不是這樣的。靜態內部類本身就是外圍類的一個成員,而不是一個獨立的對象存在的。因為沒有保存它外圍類對象,所以靜態內部類的創建不依賴于外圍類的引用,也沒有自然獲取的外圍類各種字段方法的權限。 例如:

/**
***靜態內部類
**/
public class innerClassDemo {
    public static void main(String[] arg0){
        OutterClass.InnerClass2 inner = new OutterClass.InnerClass2();
        System.out.println("--getName = "+inner.getName());
    }
}
class OutterClass{
    private String name = "OutterClassName";
    public String getName(){
        System.out.println("--OutterClass-getName-");
        return name;
    }

    static class InnerClass2{
        private String name = "InnerClassName2";
        public String getName(){
            System.out.println("--InnerClass2-getName-");
            return name;
        }
    }
}

InnerClass類添加static關鍵字,使得它變成一個靜態內部類,那么使用OutterClass.this.name的時候會提示錯誤,說明無法訪問外圍類的非靜態字段

局部內部類

定義:定義在外圍類的內部代碼塊中,它不時外圍類的一部分,但是能訪問當前代碼塊內的變量和所有的外圍類的成員,作用的范圍類似于局部變量只在當前代碼塊內部,因為外部沒有任何引用它的路徑。
局部內部類可以訪問定義的該類作用字段中的所有變量,包括代碼塊中的局部變量,外圍類的所有成員和局部內部類的所有字段,但是代碼塊中的局部變量或方法參數只能聲明成final才能被訪問。這是多線程的問題訪問保證安全性的措施,而且這樣的好處是值是確定的。在局部內部類中,也會存在普通內部類存在的隱藏字段方法等問題,如果隱藏的是代碼塊的局部變量,那么久沒有辦法來訪問這個被隱藏的變量了。

匿名內部類

定義:擴展了某個類或實現了某個接口的的匿名類。
匿名內部類沒有顯示聲明的構造器,但是可以使用初始代碼塊來初始化。雖然匿名類使用起來很簡單,但是會降低代碼的可讀性。

接口中的嵌套

雖然可以使用,但是目前來說,接口作為一個行為協議,盡量不要在內部書寫除協議本身以外的東西。
接口中嵌套類或者接口,本質上和類中嵌套類一樣,只是把一個與接口聯系緊密的類型關聯到這個接口的內部。例如:

interface OutterInterface{
    class InnerClass{}
    interface InnerInterface{}
    InnerClass getInnerClass();
    InnerInterface getInnerInterface();
}

這樣,內部的接口或者內部的類就和外圍接口緊密的綁定在一起。注:任何在接口中定義的類都是publicstatic

類中嵌套接口

其實這個是很少很少用,目前來說,我還沒有見過,但是說明一下。它們在類中起到的作用僅僅只是組織相關類型的機制。由于接口沒有實現,所以它不能是非靜態的,默認的static關鍵字省略。

內部類標識符

一般來說,我們的Demo.java文件編譯后文件名為Demo.class文件,但是如果java文件內部包含了內部類,那么文件會將內部類分出一個class文件,內部類的文件名為外圍類名$內部類名,例如Outer實體中還有Inner類,那么編譯出來后,會存在Outer.class文件和Outer$Inner.class文件。那么如果是個匿名內部類呢,它本身就沒有名字,這種情況下,編譯器會簡單的產生一個數字作為它的標識符。例如Outer$1.class。

寫到這里已經把內部類給寫完了,雖然后面部分只用文字描述了一下,還有一些東西沒有寫在上面,具體的可以再去看看研究研究,比如匿名內部類和局部內部類的區別等。

本文參考《Thinking in Java》第10章內部類,《Java程序設計語言》第5章 嵌套類和接口

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,646評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,595評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,560評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,035評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,814評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,224評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,301評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,444評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,988評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,804評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,998評論 1 370
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,544評論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,237評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,665評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,927評論 1 287
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,706評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,993評論 2 374

推薦閱讀更多精彩內容

  • 1. Java基礎部分 基礎部分的順序:基本語法,類相關的語法,內部類的語法,繼承相關的語法,異常的語法,線程的語...
    子非魚_t_閱讀 31,721評論 18 399
  • 第一章 對象導論 對象具有狀態、行為和標識。這意味著每一個對象都可以擁有內部數據和方法,并且每一個對象都可以唯一地...
    niaoge2016閱讀 835評論 0 0
  • 以前的我懵懵懂懂,沒有思想;以前的我一分鐘刷一次朋友圈,每天在扣扣空間里發一些無聊甚至于抱怨的話;是所有男孩眼...
    雪狐白糖閱讀 209評論 0 0
  • 秋天將至,萬物呈現出凄涼衰敗,北方的候鳥已經開始飛往南方避冬。在這樣一群南飛的雁群中,有一對新結發的雁夫婦,公雁我...
    BlueCute閱讀 158評論 0 1
  • 今天是你走的第九天,還有104天。我想你,今天心情不好但是和你聊天就好一點了。回來可以看到你是我最開心的事。 昨天...
    rainll閱讀 143評論 0 0