內(nèi)部類就是定義在一個類中的另外一個類,是一種從屬關(guān)系。在沒有實際了解內(nèi)部類之前,我始終困惑,為什么要在一個類中定義另外一個類,這不是增加代碼結(jié)構(gòu)復(fù)雜度么?現(xiàn)在才大致能知道這種設(shè)計的優(yōu)勢是大于其劣勢的。比如,我們可以通過內(nèi)部類解決類的單繼承問題,外部類不能再繼承的類可以交給內(nèi)部類繼承。我們可以通過定義內(nèi)部類來實現(xiàn)一個類私屬于一個類,實現(xiàn)更好的封裝性。具體的我們接下來介紹,本文主要通過介紹內(nèi)部類的四種不同類型的定義,實例的創(chuàng)建,內(nèi)部實現(xiàn)原理以及使用場景幾種不同角度來學(xué)習(xí)內(nèi)部類。

  • 靜態(tài)內(nèi)部類

  • 成員內(nèi)部類

  • 方法內(nèi)部類

  • 匿名內(nèi)部類

一、靜態(tài)內(nèi)部類
     靜態(tài)內(nèi)部類的定義和普通的靜態(tài)變量或者靜態(tài)方法的定義方法是一樣的,使用static關(guān)鍵字,只不過這次static是修飾在class上的,一般而言,只有靜態(tài)內(nèi)部類才允許使用static關(guān)鍵字修飾,普通類的定義是不能用static關(guān)鍵字修飾的,這一點需要注意一下。下面定義一個靜態(tài)內(nèi)部類:

public class Out {    private static String name;    private int age;    public static class In{        private int age;        public void sayHello(){
            
            System.out.println("my name is : "+name);            //--編譯報錯---
            //System.out.println("my age is :"+ age);
        }
    }
}

在上述代碼中,In這個類就是一個靜態(tài)內(nèi)部類。我們說內(nèi)部類是可以訪問外部類的私有字段和私有方法的,對于靜態(tài)內(nèi)部類,它遵循一致的原則,只能訪問外部類的靜態(tài)成員。上述代碼中,外部類的非靜態(tài)私有字段age在靜態(tài)內(nèi)部類中使不允許訪問的,而靜態(tài)字段name則是可訪問的。下面我們看,如何創(chuàng)建一個靜態(tài)內(nèi)部類的實例對象。

public static void main(String [] args){
    Out.In innerClass = new Out.In();
    innerClass.sayHello();
}

靜態(tài)內(nèi)部類的實例對象創(chuàng)建還是比較簡潔的,不同于成員內(nèi)部類,它不需要關(guān)聯(lián)外部類實例(具體的下文介紹),下面我們再看一段代碼:

public class Out {    private static String name;    public static class In{        public void sayHello(){
            System.out.println(name);
            showName();
        }
    }    private static void showName(){
        System.out.println(name);
    }
}

上述代碼在內(nèi)部類中兩次訪問了外部類的靜態(tài)成員,第一次訪問了靜態(tài)字段name,第二次訪問的靜態(tài)方法showName。在我們反編譯這個類之前,首先需要知道的是,所謂的內(nèi)部類的概念只是出現(xiàn)在編譯階段,對于jvm層是沒有內(nèi)部類這個概念的。也就是說,編譯器會將一個類編譯成一個源文件,對于內(nèi)部類也是一樣,它會從它的外部類中抽離出來,增加一些與外部類的聯(lián)系,然后被編譯成一個單獨的源文件。下面我們先編譯運(yùn)行之后,利用Dj反編譯class文件看看編譯器都做了些什么事情。

//這是我們的Out外部類public class Out{    //省去了一些不重要的部分
    private static void showName()    {
        System.out.println(name);
    }    private static String name;    
    static String access$000(){return name;}    static void access$100(){showName();}

}
//這是我們的內(nèi)部類public static class Out$In{    public void sayHello()
    {
        System.out.println(Out.access$000());        Out.access$100();
    }    public Out$In()
    {
    }
}

相信大家也已經(jīng)看出來這兩者之間的某種聯(lián)系,編譯器將Out這個類編譯成兩個獨立的class源文件。對于Out中所有的私有成員(也就是內(nèi)部類分離出去之后不能訪問的成員),增設(shè)了可供調(diào)用的access$xxx方法,從而實現(xiàn)內(nèi)部類與外部類之間的聯(lián)系。這就是他們的本質(zhì)。

至于使用場景,一般來說,對于和外部類聯(lián)系緊密但是并不依賴于外部類實例的情況下,可以考慮定義成靜態(tài)內(nèi)部類。下面我們看稍顯復(fù)雜的成員內(nèi)部類。

二、成員內(nèi)部類
     我們說了,四種不同類型的內(nèi)部類都各自有各自的使用場景,靜態(tài)內(nèi)部類適合于那種和外部類關(guān)系密切但是并不依賴外部類實例的情況。但是對于需要和外部類實例相關(guān)聯(lián)的情況下,可以選擇將內(nèi)部類定義成成員內(nèi)部類。以下代碼定義了一個簡單的成員內(nèi)部類:

public class Out {    private String name;    public void showName(){
        System.out.println("my name is : "+name);
    }    public class In{        public void sayHello(){
            System.out.println(name);
            Out.this.showName();
        }
    }
}

以上定義了一個簡單的內(nèi)部類In,我們的成員內(nèi)部類可以直接訪問外部類的成員字段和成員方法,因為它是關(guān)聯(lián)著一個外部類實例的。下面我們看看在外部是如何創(chuàng)建該內(nèi)部類實例的。

public static void main(String [] args){    Out out = new Out();    Out.In in = out.new In();    in.sayHello();
}

因為成員內(nèi)部類是關(guān)聯(lián)著一個具體的外部類實例的,所以它的實例創(chuàng)建必然是由外部類實例來創(chuàng)建的。對于實例的創(chuàng)建,我們只需要記住即可,成員內(nèi)部類的實例創(chuàng)建需要關(guān)聯(lián)外部類實例對象,靜態(tài)內(nèi)部類實例創(chuàng)建相對簡單。下面我們主要看看在編譯階段編譯器是如何保持內(nèi)部類對外部類成員信息可訪問的。

//反編譯的Out外部類源碼public class Out{    //省略部分非核心代碼
    public void showName()    {
        System.out.println((new StringBuilder()).append("my name is : ").append(name).toString());
    }    private String name;    
    static String access$000(Out o){return o.name;}
}
//反編譯的內(nèi)部類In源碼public class Out$In{    public void sayHello()
    {
        System.out.println(Out.access$000(Out.this));
        showName();
    }    final Out this$0;    public Out$In()
    {
        this.this$0 = Out.this;
        super();
    }
}

由上述代碼其實我們可以知道,當(dāng)我們利用外部類實例創(chuàng)建內(nèi)部類實例的時候,會將外部類實例作為初始資源傳入內(nèi)部類構(gòu)造過程。這樣我們就可以通過該實例訪問外部類所有的成員信息,包括私有成員。(顯式增加了暴露方法)

至于使用場景,對于那種要高度依賴外部類實例的情況下,定義一個成員內(nèi)部類則會顯的更加明智。

三、方法內(nèi)部類
     方法內(nèi)部類,顧名思義,定義在一個方法內(nèi)部的類。方法內(nèi)部類相對而言要復(fù)雜一些,下面定義一個方法內(nèi)部類:

public class Out {    private String name;    public void sayHello(){        class In{            public void showName(){
                System.out.println("my name is : "+name);
            }
        }

        In in = new In();        in.showName();
    }
}

我們定義了一個類,在該類中又定義了一個方法sayHello,然而在該方法中我們定義了一個內(nèi)部類,類In就是一個方法內(nèi)部類。我們的方法內(nèi)部類的生命周期不超過包含它的方法的生命周期,也就是說,方法內(nèi)部類只能在方法中使用。所以在聲明的時候,任何的訪問修飾符都是沒有意義的,于是Java干脆不允許使用任何的訪問修飾符修飾方法內(nèi)部類。其中還需要注意一點的是,定義和使用時兩回事,別看那一大串定義類的代碼,你實際想要使用該類,就必須new對象,而對于方法內(nèi)部類而言,只能在方法內(nèi)部new對象。這就是方法內(nèi)部類的簡單介紹,下面我們看看其實現(xiàn)原理。

有關(guān)方法內(nèi)部類的實現(xiàn)原理其實是和成員內(nèi)部類差不太多的,也是在內(nèi)部類初始化的時候為其傳入一個外部類實例,區(qū)別在哪呢?就在于方法內(nèi)部類是定義在具體方法的內(nèi)部的,所以該類除了可以通過傳入的外部實例訪問外部類中的字段和方法,對于包含它的方法中被傳入的參數(shù)也會隨著外部類實例一起初始化給內(nèi)部類。

毋庸置疑的是,方法內(nèi)部類的封裝性比之前介紹的兩種都要完善。所以一般只有在需要高度封裝的時候才會將類定義成方法內(nèi)部類。

四、匿名內(nèi)部類
     可能內(nèi)部類的所有分類中,匿名內(nèi)部類的名號是最大的,也是我們最常用到的,多見于函數(shù)式編程,lambda表達(dá)式等。下面我們重點看看這個匿名內(nèi)部類。

匿名內(nèi)部類就是沒有名字的內(nèi)部類,在定義完成同時,實例也創(chuàng)建好了,常常和new關(guān)鍵字緊密結(jié)合。當(dāng)然,它也不局限于類,也可以是接口
,可以出現(xiàn)在任何位置。下面我們定義一個匿名內(nèi)部類:

//首先定義一個普通類public class Out {    private String name;    private void sayHello(){
        System.out.println("my name is :" + name);
    }
}
//定義和使用一個匿名內(nèi)部類public static void main(String [] args){
    Out out = new Out(){
        @Override        public void sayHello(){
            System.out.println("my name is cyy");
        }        public void showName(){
            System.out.println("hello single");
        }
    };    out.sayHello();
}

從上述代碼中可以很顯然的讓我們看出來,我們的匿名內(nèi)部類必定是要依托一個父類的,因為它是沒有名字的,無法用一個具體的類型來表示。所以匿名內(nèi)部類往往都是通過繼承一個父類,重寫或者重新聲明一些成員來實現(xiàn)一個匿名內(nèi)部類的定義。實際上還是利用了里式轉(zhuǎn)換原理。

從中我們也可以看到,一個匿名內(nèi)部類定義的完成就意味著該內(nèi)部類實例創(chuàng)建的完成。下面我們看看其實現(xiàn)原理:

//反編譯出來的匿名內(nèi)部類static class Test$1 extends Out{    Out out;
    public void sayHello()
    {        System.out.println("my name is cyy");
    }    Test$1(Out o)
    {        this.out = o;
    }
}

其實在看了上述三種內(nèi)部類的原理之后,反而覺得匿名內(nèi)部類的實現(xiàn)較為簡單了。主要思路還是將內(nèi)部類抽離出來,通過初始化傳入外部類的實例以達(dá)到對外部類所有成員的訪問。只是在匿名內(nèi)部類中,被依托的父類不是他的外部類。匿名內(nèi)部類的主要特點在于,沒有名字,對象只能被使用一次,可以出現(xiàn)在任意位置。所以它的使用場景也是呼之欲出,對于一些對代碼簡潔度有所要求的情況下,可首選匿名內(nèi)部類。

以上完成了對四種內(nèi)部類的簡單介紹,對于他們各自實現(xiàn)的原理也都已經(jīng)介紹過了。其實大致相同,由于jvm對每個類都要求一個單獨的源碼文件,所以編譯階段就完成了分離的操作,但是在分離的過程中又要保持內(nèi)部類和外部類之間的這種聯(lián)系,于是編譯器添加了一些接口保持這種信息共享的結(jié)構(gòu)。使用內(nèi)部類可以大大增加程序的封裝性,使得代碼整體簡潔度較高。

本文主要來自于多篇文章的學(xué)習(xí),總結(jié)不到之處,望指出!

http://www.cnblogs.com/yangming1996/p/7217829.html