你好,我是java中的內部類

內部類日常用的仍是挺多的,所以研究和總結內部類就顯得很是重要。內部類,顧名思義就是將一個類的定義放在另外一個類的內部進行,這就是內部類。內部類較難理解,可是功能強大,理解和運用內部類對提升編碼水平有很是大的幫助。html

初次邂逅---內部類存在的原因

舉一個常常看到的例子:java

/**外部類**/
public class Outer {
    /**內部類(內部是相對於外部而言)**/
    class Inner{
        //doSomething
    }
}

如今問題來了,爲什麼將一個類定義在某個類的內部,難道這不違反程序設計的單一職責原則嗎?一個類定義在一個java源文件中不香嗎?所以在使用內部類以前,瞭解使用內部類的理由顯得尤其重要,我的以爲學知識帶着目的來學,印象可能更深一些,畢竟有實際的例子來輔助記憶。安全

曾經讀過一本書《Think in java》(像java同樣思考),記得裏面有一句關於內部類的話:"使用內部類最吸引人的緣由是:每一個內部類都能獨立地繼承一個(接口的)實現,因此不管外圍類是否已經繼承了某個(接口的)實現,對於內部類都沒有影響"。固然這句話如今幾乎成了勸人學內部類的定理。其實這句話透露出一些信息:一、內部類也能夠繼承某個類或實現某個接口;二、能夠突破Java中類的單繼承「限制」。微信

看一個例子,來加深對上述論據的理解。定義兩個類ClassA和ClassB,很顯然ClassC只能實現其中的任意一個類(此處繼承ClassA),可是當你在ClassC的內部定義一個內部類InnerClassC,讓它繼承ClassB時,你會發如今內部類InnerClassC中是能夠同時訪問到ClassA和ClassB類中的方法:ide

public class ClassA {
    public void classAmethod(){
        System.out.println("classAmethod");
    }
}

public class ClassB {
    public void classBmethod(){
        System.out.println("classBmethod");
    }
}

public class ClassC extends ClassA {
    class InnerClassC extends ClassB{
        public InnerClassC(){
            classAmethod();
            classBmethod();
        }
    }
}

因此使用內部類最大的優勢就在於,它能解決多重繼承的問題。若是開發者不須要解決多重繼承這一問題,那麼可使用其餘方式,殺雞焉用牛刀?函數

固然內部類的優勢不只僅只有上述一點,《Think in java》這本書中還列舉了5點做爲補充:一、內部類能夠有多個實例,每一個實例都有本身的狀態信息,且與其餘外圍對象的信息相互獨立。二、在單個外圍類中,可讓多個內部類以不一樣的方式繼承同一個類或者實現同一個接口。三、內部類對象的建立並不必定依賴於外圍類對象。四、內部類並無使人迷惑的「is-a」關係(繼承關係),它就是一個獨立的實體。五、內部類提供了更好的封裝,除了該外圍類,其餘類都不能訪問。測試

相識---內部類基礎

接下來介紹內部類的基礎知識,同時對前面的例子進行更深層次的分析與研究。來看一段代碼:ui

//ClassA.class
public class ClassA {
    private String Aname;
    //getter和setter方法
    public void classAmethod(){
        System.out.println("classAmethod");
    }
}

//ClassB.class
public class ClassB {
    private String Bname;
    //getter和setter方法
    public void classBmethod(){
        System.out.println("classBmethod");
    }
}

//ClassC.class
public class ClassC extends ClassA {
    private String Cname;
    //getter和setter方法
    class InnerClassC extends ClassB{
         public InnerClassC(){
            Cname = "I am innerClassC";
            System.out.println(getAname());
            System.out.println(getBname());
            System.out.println(getCname());
        }

        public void show(){
             System.out.println("Cname:"+getCname());
        }
    }

    public static void main(String[] args){
        ClassC c = new ClassC();
        ClassC.InnerClassC ic = c.new InnerClassC();
        ic.show();
    }
}

運行結果爲:this

null
null
I am innerClassC
Cname:I am innerClassC

分析一下上述代碼的含義:定義了兩個類ClassA和ClassB,且均在內部設置了Xname屬性及對應方法和一個classXmethod方法。接着再定義了一個ClassC類,它做爲外部類繼承ClassA類,同時內部也設置了Xname屬性及對應方法。ClassC內部類InnerClassC繼承了ClassB類,並定義了一個無參的構造方法。在這個無參的構造方法中,能夠訪問外部ClassC類的屬性,哪怕是private修飾的。編碼

爲什麼內部類能夠訪問外部類的全部屬性(包括私有)?緣由在於建立某個外部類的內部類對象時,此時內部類對象一定會捕獲一個指向該外部類對象的引用,只要內部類在訪問外部類的成員時,就會使用這個引用來選擇外部類的成員。說白了,就是這裏內部類的建立須要依賴外部類對象(注意是這裏,不是全部後續會說明)。

繼續回到代碼中,在ClassC外部類中定義了一個main方法。在這個main方法中首先實例化一個外部類對象,接着使用ClassC.InnerClassC ic = c.new InnerClassC()來實例化內部類對象。注意看這個內部類對象的引用爲ClassC.InnerClassC,對象類型都得依賴外部類,且使用了外部類對象的new方法來建立內部類對象。

仔細查看內部類InnerClassC無參構造方法可知,竟然能夠直接使用getAname()getCname方法,這都是外部類的。按照常理開發者都習慣使用this來代指本類,super代指父類。嘗試將InnerClassC內部類無參構造方法中的代碼修改成以下:

public InnerClassC(){
            Cname = "I am innerClassC";
            System.out.println(this.getAname());
            System.out.println(this.getBname());
            System.out.println(this.getCname());
        }

但是IDEA出現錯誤提示:

再次驗證了前面的論述:這裏的getAname()getCname方法都是外部類的,而它又能夠直接使用,不須要顯式調用。說明內部類中存在對外部類對象的一個隱式引用,其實就是ClassC.this

所以當開發者須要在內部類中生成一個外部類的引用時,使用ClassC.this便可。

內部類初學者都有一個疑問,這個包含內部類的java源文件在編譯成class文件後,其中的內部類還存在嗎?這個問題今天有必要驗證一下。進入到ClassC類所在的文件夾,使用javac *.java命令進行編譯(注意不能只單純編譯ClassC文件,其中包含了其餘類的引用,須要同時編譯):

發現問題了,這個ClassC類竟然編譯出兩個文件。查看一下這個ClassC$InnerClassC字節碼文件:

再來查看ClassC字節碼文件:

能夠看到這兩個class文件已經再也不是一個類了,是具備聯繫的兩個對象。

間接說明內部類是個編譯時的概念,一旦編譯成功後,就與外部類屬於兩個徹底不一樣但具備必定聯繫的類。

相知---內部類分類

內部類一共分爲4種,分別是:成員內部類、靜態內部類、方法內部類和匿名內部類。

成員內部類

成員內部類,顧名思義,內部類做爲一個成員而存在於外部類中。它是最普通的內部類,正如前面所見,它能夠訪問外部類的全部成員屬性和方法,哪怕是private修飾的。可是外部類想要訪問內部類的成員屬性和方法,則須要經過內部類的實例才能進行訪問。

如今有一個問題,在成員內部類中是否能使用static關鍵詞?這個static關鍵詞很是魔性,後續會專門出一篇文章來聊聊它。

答案是不能,IDEA提示成員內部類中不能包含任何靜態聲明。其實很好理解,緣由在於成員屬性或者方法都是依賴於對象而存在,而靜態屬性或者方法並非,它與類有關。

舉一個比較典型的成員內部類實例來加深理解:

public class Outer{
   private int outerVar = 1;
   private int commonVar = 2;
   private static int outStaticVar = 3;
   //getter和setter方法
   /**成員方法**/
   public void outerMethod(){
       System.out.println("outerMethod");
   }
    /**靜態方法**/
    public static void outerStaticMethod(){
        System.out.println("outerStaticMethod");
    }

    /**內部類**/
    public class Inner{
        /**成員屬性**/
        private int commonVar = 102;

        /**無參構造方法**/
        public Inner(){};
        /**成員方法,用來訪問外部類的屬性和方法**/
        public void show(){
            //當內部類和外部類屬性同名時,默認調用的是內部類的屬性
            System.out.println("內部類的commonVar屬性值:"+commonVar);
            //當內部類和外部類屬性同名時,獲取同名外部類屬性(外部類名.this.同名屬性)
            System.out.println("外部類的commonVar屬性值:"+Outer.this.commonVar);
            //內部類訪問外部類的成員屬性
            System.out.println("外部類的outerVar屬性值:"+outerVar);
            //內部類訪問外部類的靜態屬性
            System.out.println("外部類的outStaticVar屬性值:"+outStaticVar);
            //內部類訪問外部類的成員方法
            outerMethod();
            //內部類訪問外部類的靜態方法
            outerStaticMethod();
        }
    }

    public static void main(String[] args){
        Outer outer = new Outer();
        Outer.Inner inner = outer.new Inner();
        inner.show();
    }
}

運行結果:

內部類的commonVar屬性值:102
外部類的commonVar屬性值:2
外部類的outerVar屬性值:1
外部類的outStaticVar屬性值:3
outerMethod
outerStaticMethod

既然是做爲外部類的成員而存在,那麼其餘的類是否也能訪問呢?定義一個Other類,裏面的代碼爲:

public class Other {
    public static void main(String[] args){
        Outer outer = new Outer();
        Outer.Inner inner = outer.new Inner();
        inner.show();
    }
}

確定是沒有問題,可是是否以爲代碼重複太明顯了,所以合適的作法是在Outer類中定義一個get方法用於獲取其成員內部類,特別是在內部類構造方法無參的狀況下:

//用於獲得一個內部類對象
    public Inner getInner(){
        return new Inner();
    }

固然有參構造方法也不麻煩,做爲參數傳進去便可。

成員內部類小結。經過前面的介紹,能夠知道成員內部類具備如下5個特色:

  • 成員內部類做爲外部類的成員而存在,能夠被任意修飾符修飾;
  • 成員內部類能夠直接訪問外部類的全部屬性和方法,哪怕是private/static修飾的;
  • 成員內部類依賴於外部類,所以內部類對象的建立必須依賴於外部類對象;
  • 成員內部類中不能包含任何靜態聲明;
  • 成員內部類能夠與外部類屬性、方法重名,但默認調用的是內部類屬性/方法。如想訪問外部類屬性/方法,可使用外部類名.this.同名屬性來訪問。

靜態內部類

顧名思義,靜態內部類就是被static修飾的內部類,既然被static修飾,說明它比較特殊。順便提一下static能夠修飾成員變量、方法、代碼塊及內部類,這一點後續會有文章進行介紹。前面也說過被static修飾的東西都不依賴對象而存在,而是依賴於類。

在前面介紹成員內部類時,說了這麼一句話:成員內部類對象的建立須要依賴於外部類對象。可是如今要談的靜態內部類對象的建立就不依賴外部類對象,也就意味着靜態內部類中不能使用外部類中任何非static修飾的變量/方法。

一樣舉一個比較典型的靜態內部類實例來加深理解:

public class Outer {
    private int outerVar = 1;
    private static int commonVar = 2;
    private static int outStaticVar = 3;
    //getter和setter方法
    /**成員方法**/
    public void outerMethod(){
        System.out.println("outerMethod");
    }
    /**靜態方法**/
    public static void outerStaticMethod(){
        System.out.println("outerStaticMethod");
    }
    /**靜態代碼塊**/
    static {
        System.out.println("out static block");
    }

    /**靜態內部類**/
    public static class StaticInner{
        /**成員屬性**/
        private int innerVar = 101;
        /**靜態屬性**/
        private static int commonVar = 102;
        private static int innerStaticVar = 103;
        /**靜態代碼塊**/
        static {
            System.out.println("inner static block");
        }
        /**成員方法**/
        public void show(){
            //當靜態內部類和外部類屬性同名時(只能是static屬性),默認調用內部類的靜態屬性
            System.out.println("內部類的commonVar屬性值:"+commonVar);
            //當靜態內部類和外部類屬性同名時(只能是static屬性),獲取同名外部類屬性(外部類名.同名屬性)
            System.out.println("外部類的commonVar屬性值:"+OuterA.commonVar);
            //靜態內部類訪問自身成員屬性
            System.out.println("內部類的innerStaticVar屬性值:"+innerStaticVar);
            //靜態內部類訪問外部類的靜態屬性
            System.out.println("外部類的outStaticVar屬性值:"+outStaticVar);
            //靜態內部類訪問外部類的靜態方法
            outerStaticMethod();
        }
        /**靜態方法**/
        public static void innerStaticMethod(){
            System.out.println("innerStaticMethod");
        }
    }
    public static void main(String[] args){
        System.out.println(StaticInner.innerStaticVar);
        StaticInner.innerStaticMethod();
        new StaticInner().show();
    }
}

猜猜看運行結果:(仔細體會一些裏面各個組件的執行順序)

out static block
inner static block
103
innerStaticMethod
內部類的commonVar屬性值:102
外部類的commonVar屬性值:2
內部類的innerStaticVar屬性值:103
外部類的outStaticVar屬性值:3
outerStaticMethod

這裏須要注意的一點就是在靜態內部類,準確來講是被static修飾的java組件(屬性、方法、代碼塊,內部類)而言,其內部都不能使用this和super,由於它缺乏一個隱式的this和super,被static修飾的組件與對象無關,僅與類有關。

一樣試試其餘的類是否能夠訪問某外部類中的靜態內部類,定義一個Other類,裏面的代碼爲:

public class Other {
    public static void main(String[] args){
        //訪問某外部類中靜態內部類的靜態方法時,靜態內部類被加載,請注意此時外部類未被加載
        Outer.StaticInner.innerStaticMethod();
        //訪問某外部類中靜態內部類的成員方法
         new Outer.StaticInner().show();
    }
}

運行結果爲:

inner static block  >>>>>>>看到沒有外部類的靜態代碼塊沒有被初始化
innerStaticMethod
內部類的commonVar屬性值:102
out static block
外部類的commonVar屬性值:2
內部類的innerStaticVar屬性值:103
外部類的outStaticVar屬性值:3
outerStaticMethod

請注意,當你直接訪問靜態內部類中的靜態方法時,因爲它不依賴與外部類,所以外部類是不會被調用的,可從外部類的靜態代碼塊沒有被初始化這一點獲得證實。

靜態內部類小結。經過前面的介紹,能夠知道靜態內部類具備如下5個特色:

  • 靜態內部類內部能夠包含任意信息;
  • 靜態內部類只能訪問外部類的static屬性/方法;
  • 靜態內部類能夠獨立存在,不依賴於其外部類;
  • 可經過外部類名.內部類名.static屬性/方法來直接訪問內部類的static屬性/方法;
  • 靜態內部類能夠與外部類被static修飾的屬性、方法重名,但默認調用的是內部類的屬性/方法。如想訪問外部類被static修飾的屬性/方法,可使用外部類名.同名屬性來訪問。

局部內部類

顧名思義,局部內部類就是定義在方法和做用域中,通常用來解決較爲複雜的問題。既然是局部內部類,那麼它就不能被任何訪問修飾符修飾,且只能在該定義的方法或做用域中使用。注意局部內部類中不能使用static關鍵字。

一樣舉一個比較典型的局部內部類實例來加深理解:

public class OuterB {
    private int outerVar = 1;
    private int commonVar = 2;
    private static int outStaticVar = 3;
    //getter和setter方法
    /**成員方法**/
    public void outerMethod(){
        System.out.println("outerMethod");
    }
    /**靜態方法**/
    public static void outerStaticMethod(){
        System.out.println("outerStaticMethod");
    }

    /**外部類的main方法**/
    public static void main(String[] args){
        OuterB outerB = new OuterB();
        outerB.outerCreateMethod("1234");
    }

    /**成員方法,內部定義局部內部類**/
    public void outerCreateMethod(String password){
        /**方法內定義的變量**/
        Boolean flag = true;

        /**局部內部類,注意前面不能使用訪問修飾符**/
        class Inner{
            /**局部內部類成員屬性**/
            private int innerVar = 101;
            private int commonVar = 102;

            /**局部內部類成員方法**/
            public void show(){
                //當局部內部類和外部類屬性同名時,默認調用局部內部類的同名屬性
                System.out.println("局部內部類的commonVar屬性值:"+commonVar);
                //當局部內部類和外部類屬性同名時,獲取同名外部類屬性(外部類名.this.同名屬性)
                System.out.println("外部類的commonVar屬性值:"+OuterB.this.commonVar);
                //局部內部類訪問自身成員屬性
                System.out.println("局部內部類的innerVar屬性值:"+innerVar);
                //局部內部類訪問外部類的成員屬性
                System.out.println("外部類的outerVar屬性值:"+outerVar);
                //局部內部類訪問外部類的靜態屬性
                System.out.println("外部類的outStaticVar屬性值:"+outStaticVar);

                //局部內部類訪問定義它的方法內的參數
                System.out.println("outerCreateMethod方法中傳入的參數password:"+password);
                //局部內部類訪問定義它的方法內的局部變量
                System.out.println("outerCreateMethod方法中定義的flag:"+flag);

                //局部內部類訪問外部類的靜態方法
                outerStaticMethod();
                //局部內部類訪問外部類的成員方法
                outerMethod();
            }
        }

        /**只能在局部內部類定義的方法內才能訪問到它**/
        Inner inner = new Inner();
        System.out.println("局部內部類的commonVar屬性值:"+inner.commonVar);
        System.out.println("局部內部類的innerVar屬性值:"+inner.innerVar);
        inner.show();
    }
}

猜一猜運行結果:

局部內部類的commonVar屬性值:102
局部內部類的innerVar屬性值:101
局部內部類的commonVar屬性值:102
外部類的commonVar屬性值:2
局部內部類的innerVar屬性值:101
外部類的outerVar屬性值:1
外部類的outStaticVar屬性值:3
outerCreateMethod方法中傳入的參數password:1234
outerCreateMethod方法中定義的flag:true
outerStaticMethod
outerMethod

經過上面的例子,你們都知道了,局部內部類只能在定義它的方法或者做用域內使用,同時局部內部類的前面不能有任何訪問修飾符。除此以外,其他的用法和成員內部類很是類似,所以徹底能夠認爲局部內部類只是把做用範圍縮小了,縮到只能在定義它的方法或者做用域內生效的一個「特殊」的成員內部類。

不知你是否注意到局部內部類中這幾行代碼,它用於輸出局部內部類定義所在方法的參數和局部變量(沒法修改參數和變量的值),看到下面的寫法,甚至以爲局部內部類能夠直接訪問方法內的局部變量和參數???沒錯,可是有條件。

//局部內部類訪問定義它的方法內的參數
System.out.println("outerCreateMethod方法中傳入的參數password:"+password);
//局部內部類訪問定義它的方法內的局部變量
System.out.println("outerCreateMethod方法中定義的flag:"+flag);

什麼條件呢?前面設置flag爲true,可是後面你修改了,將其設置爲false:

/**方法內定義的變量**/
        Boolean flag = true;
        flag = false;

這時候局部內部類拋出異常:

IDEA的提示是說"flag變量從內部類中訪問,須要final或有效的final"。言外之意:若是局部內部類想訪問定義它的方法內的局部變量,那麼這個變量要麼前面使用final修飾,要麼第一次賦值後再也不修改(引用類型是指向不能改變)。特別注意JDK1.8以前(不含JDK1.8)只能訪問被final修飾的變量,也就是隻有前一種方式,後面這種方式是後來添加的。對於這種特殊的變量限制,在局部內部類訪問定義它的方法的參數時,也一樣適用。

局部內部類小結。經過前面的介紹,能夠知道局部內部類具備如下6個特色:

  • 局部內部類只能定義在方法或者做用域內;
  • 局部內部類前不能有任何訪問修飾符;
  • 局部內部類中不能包含任何靜態聲明;
  • 當被定義的方法/做用域中的參數、變量前有final修飾或者參數、變量的值在賦值後再也不修改時,局部內部類能夠直接訪問被定義的方法/做用域中的參數、變量,可是沒法修改;
  • 局部內部類能夠直接訪問外部類的全部屬性和方法,哪怕是private/static修飾的;
  • 局部內部類能夠與外部類的屬性、方法重名,但默認調用的是局部內部類的屬性/方法。如想訪問外部類的屬性/方法,可使用外部類名.this.同名屬性來訪問。

匿名內部類

顧名思義,匿名內部類就是沒有名字的內部類,既然沒有名字,那麼就沒法使用訪問修飾符,也沒有構造方法。匿名內部類用的比較多,所以對於它的研究就顯得尤其重要。其實你徹底能夠認爲匿名內部類是一個沒有名字的局部內部類。

匿名內部類的建立格式爲:

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

新手可能第一眼還沒明白怎麼回事,就建立了一個匿名內部類。從這個建立格式能夠看出匿名內部類必須繼承一個類或者實現一個接口。匿名內部類的聲明不能使用class關鍵字,而是直接使用new來生成一個對象的隱式引用。

匿名內部類的用法較爲特殊,舉一個比較典型的實例來加深理解:

package com.envy.inner;

public class OuterC {

    public interface Fruit{
        void eat();
    };

    public static Fruit getFruit(String description){
        Boolean flag = true;
        return new Fruit() {
            @Override
            public void eat() {
                System.out.println("獲取被定義類的參數信息:"+description);
                System.out.println("獲取被定義類的局部變量:"+flag);
            }
            //注意後面一行的分號不能少!
        };

    }

    public static void main(String[] args){
        /**外部類調用匿名內部類中的方法**/
        OuterC.getFruit("真香").eat();
    }
}

運行結果:

獲取被定義類的參數信息:真香
獲取被定義類的局部變量:true

從上面的代碼中能夠知道幾點:一、匿名內部類必須繼承一個類或者實現一個接口(只能二選一);二、匿名內部類沒有類名,因此也沒有構造方法;三、匿名內部類沒有訪問修飾符;四、使用匿名內部類時,new後面的類/接口必須存在,其次可能要重寫new以後類/接口的某個或某些方法;五、匿名內部類訪問方法參數/變量時,一樣也存在和局部內部類同樣的訪問限制;六、匿名內部類不能是抽象類,所以必須實現繼承類或者實現接口的全部抽象方法。

相思---訪問限制思考

如今來思考一下爲什麼在局部內部類(包含匿名內部類)中訪問定義它的方法的參數或者局部變量時,會有一個訪問限制?即要求這個局部變量(方法參數要)要麼前面使用final修飾,要麼第一次賦值後再也不修改(引用類型是指向不能改變)。便於說明這裏統一爲必須使用final關鍵詞修飾。

若是開發者不按照要求進行設置,那麼強行訪問IDEA就會提示圖片所示異常:

便於研究,這裏提供一個測試了,裏面的代碼爲:

public class OuterD {
    public interface Fruit{
        void eat();
    }

    public Fruit getFruit(String name){
        boolean flag = true;
        return new Fruit() {
            @Override
            public void eat() {
                System.out.println("parameter:"+name);

                System.out.println("local variable:"+flag);
            }
        };
    }
}

先嚐試使用javac *.java命令編譯一下這個測試類,因爲其中包含一個接口,所以能夠猜猜最後會編譯出三個class字節碼文件:

OuterD.class文件是外部類編譯後生成的文件,查看一下其中的內容:

package com.envy.test;

public class OuterD {
    public OuterD() {
    }

    public OuterD.Fruit getFruit(final String var1) {
        final boolean var2 = true;
        return new OuterD.Fruit() {
            public void eat() {
                System.out.println("parameter:" + var1);
                System.out.println("local variable:" + var2);
            }
        };
    }

    public interface Fruit {
        void eat();
    }
}

能夠發現編譯後外部類內部自動添加了其無參的構造方法,其次對於定義內部類的方法的參數和局部變量前面也都自動添加了final關鍵詞,筆者此處使用的是java1.8,因此也就知道了,爲什麼1.8及之後能夠容許局部變量/方法參數第一次賦值後再也不修改(引用類型是指向不能改變)也是可能的,其實編譯後仍是會添加final關鍵詞,只是再也不強制要求開發者添加了。且能夠發現它經過使用外部類.接口名這種方式對外提供了內部類的訪問方式。

再來查看一下OuterD$Fruit.class文件,能夠發現其實它就是Fruit接口編譯後的文件:

package com.envy.test;

public interface OuterD$Fruit {
    void eat();
}

能夠發現編譯後在外部類文件中訪問內部類信息都是經過.形式,可是在外部類文件外則是使用$,言外之意爲只有某個類中包含內部類,編譯後纔會有$存在。

再來看這個OuterD$1.class文件,顯然這個就是匿名內部類編譯後的文件,由於它沒有名字只能使用1來標識:

package com.envy.test;

import com.envy.test.OuterD.Fruit;

class OuterD$1 implements Fruit {
    OuterD$1(OuterD var1, String var2, boolean var3) {
        this.this$0 = var1;
        this.val$name = var2;
        this.val$flag = var3;
    }

    public void eat() {
        System.out.println("parameter:" + this.val$name);
        System.out.println("local variable:" + this.val$flag);
    }
}

看到問題的關鍵了,這裏竟然存在一個有參的構造方法,第一個參數是var1(外部類對象),第二個參數是被定義方法傳入的參數,第三個參數是被定義方法內定義的局部變量的值。

因此匿名內部類並非直接調用被定義的方法內傳遞的參數或者局部變量,而是使用本身的構造函數對傳入的參數進行了備份,匿名內部類內的方法調用的其實是本身的屬性,並非外部傳進來的參數或者局部變量。(eat方法就說明了這一點)

不過這個彷佛好像和必須使用final關鍵詞聯繫不大,其實否則,前面說了內部類中方法其實調用的是本身的屬性,那麼就能夠對內部類的屬性進行修改,而不會影響到外部被定義方法中的方法參數或者局部變量。按照常理來講的確是這樣的,可是當筆者嘗試在匿名內部類的方法中修改「方法參數或者局部變量」時,IDEA報錯了:

其實從開發者角度來講,既然將形參var2賦值給了val$name屬性,那麼val$name其實就是var2,二者是同一個對象:

OuterD$1(OuterD var1, String var2, boolean var3) {
        this.this$0 = var1;
        this.val$name = var2;
        this.val$flag = var3;
    }

若是方法內的val$name屬性發生了變化,相應的形參var2的值也應該跟着變化,可是每每形參var2的值並不會變,所以這就形成了嚴重的安全問題,因此才規定使用final關鍵詞來規避這一問題的產生。

看到這裏也就明白了其實就是一個拷貝問題,Java不存在指針,那麼爲避免拷貝的值發生變化(如某局部變量被修改了,而內部類拷貝的值沒有跟着變化,這樣就形成了內外值不一致的狀況),因而引入final關鍵詞來保證該值永遠不會變化。

有人可能有這麼一個疑問,明明在被定義方法內修改的局部變量是做爲一個形參傳入到匿名內部類之中,形參變了,它賦值給內部類中的屬性,這個屬性怎麼不會跟着變化呢?

爲了解決這個疑問,下面採用Debug來一步步進行調試:

package com.envy.inner;

public class OuterD {
    public interface Fruit{
        void eat();
    }

    public Fruit getFruit(String name){
        boolean flag = true;
        return new Fruit() {
            @Override
            public void eat() {
                System.out.println("parameter:"+name);
                System.out.println("local variable:"+flag);
            }
        };
    }

    public static void main(String[] args){
        OuterD outerD = new OuterD();
        Fruit fruit= outerD.getFruit("envy");
        fruit.eat();
    }
}

執行順序以下:

仔細看執行順序,說明getFruit方法是在eat方法前執行的,也就是eat方法中是直接保存了getFruit方法中對象的值。請注意這個匿名內部類中的eat方法是能夠被Fruit對象所調用,不過晚於getFruit方法的執行罷了,getFruit方法都執行完了,保存在棧中的局部變量的生命週期也結束了。

須要注意的是,只有匿名內部類訪問的方法參數或者局部變量前才須要添加final或者首次賦值後再也不進行修改,對於沒有訪問的就沒有這個限制了:

相戀---沒有構造方法也能初始化

在前面屢次提到,因爲匿名內部類沒有名字,所以沒有構造方法,那麼它是否也能初始化呢?答案是確定的,可使用構造代碼塊!舉一個比較典型的例子:

package com.envy.inner;

public class OuterE {

    public interface Fruit{
        double getFruitPrice();
        String getFruitName();
    };

    public Fruit getFruit(final String name, final double price){
        return new Fruit() {
            String fruitName;
            double fruitPrice;
            {
                fruitName = name;
                fruitPrice = price;
            }
            public double getFruitPrice() {
                return fruitPrice;
            }
            public String getFruitName(){
                return fruitName;
            }
        };
    }

    public static void main(String[] args){
        OuterE outerE = new OuterE();
        Fruit fruit = outerE.getFruit("蘋果",2.8);
        System.out.println(fruit.getFruitPrice());
        System.out.println(fruit.getFruitName());
    }
}

運行結果:

2.8
蘋果

請注意這裏的Fruit接口中必須定義getFruitPricegetFruitName方法,不然你沒法直接經過fruit.getFruitPrice()等形式來調用匿名內部類中屬性的getter方法,由於匿名內部類沒有名字,因此沒法來調用它的方法,只能藉助於它實現類的同名方法的實現來完成方法調用。

那麼關於內部類就先介紹到這裏,裏面涉及到的知識點仍是挺多的,須要好好複習和消化。

參考文章:詳解內部類淺談Java內部類,感謝大佬們的指點。

獲取更多技術文章和信息,請關注個人我的微信公衆號:餘思博客,歡迎你們前來圍觀和玩耍:

image

相關文章
相關標籤/搜索