天天看點

【Java核心技術卷】深入了解Java的内部類

通過圖示進行分析:

【Java核心技術卷】深入了解Java的内部類

該圖展示了Java内部類的編譯解釋過程. 你會看到整個過程很繁瑣.

因為曆史原因, Java語言規範 和位元組碼語言規範有不重疊的部分, 最初的時候,它們是重疊的. 但是後來随着Java的發展,有新的東西需要加入,比如說泛型,但是位元組碼語言規範不能夠輕易變更,因為這個涉及到相容問題. 能夠動的隻有編譯器, 通過編譯器把Java源程式編譯成滿足位元組碼語言規範的位元組碼檔案進行執行.今天的内部類 和泛型一樣,也是後來加入的. 關于内部類的實作,其實編譯器在後面做了很多很多的東西, 雖然内部類有被Lambda表達式取代的趨勢(Lambda表達式有自己的解釋器),但是還是需要五深入了解的.

直接介紹凝練點:

1.Java源程式要遵循Java語言規範,Java編譯器按照Java語言規範來編譯Java源程式。

2.位元組碼程式要遵循位元組碼語言規範,JVM的Java解釋器/JIT編譯器按照位元組碼語言規範來解釋/編譯運作位元組碼程式。

3.Java語言與位元組碼語言是兩種不同的語言。它們的語言規範有相同處,也存在不同處

例如:

  • 相同處:均有通路控制符。一個類内部的私有成員隻能被該類的其它成員所通路,其它類無法通路該類的私有成員。
  • 不同處:

    1)内部類是Java語言的規範,位元組碼規範中無内部類指令,JVM對内部類一無所知。

2)在Java規範中一個類中不允許定義函數簽名完全相同的兩個方法,與傳回類型無關。在位元組碼規範中一個類中允許定義函數簽名完全相同而傳回類型不同的兩個方法。

4.Java編譯器

打個比方,Java語言如同中文,位元組碼語言如同英文,Java編譯器如同一個翻譯。這個翻譯,不僅要把符合中文語言規範的中文翻譯成符合英文語言規範的英文(這個翻譯是知道中文文法和英文文法的差異的),而且翻譯出來的英文在意思上和中文在意思上應該完全相同。

a)按照Java語言規範編譯Java源程式

b)生成的位元組碼程式必須滿足位元組碼語言規範

c)生成的位元組碼程式必須嚴格完成Java源程式的功能和安全

由于位元組碼規範中無内部類指令,内部類是Java語言的規範。是以,Java編譯器既要按照内部類的Java文法來編譯源程式,又要按照無内部類的位元組碼文法來生成位元組碼程式,同時生成的無内部類的位元組碼程式要完成有内部類的源程式的功能。是以編譯器是意譯,而不是直譯。

一定要記住,運作的程式是位元組碼程式而不是Java源程式,記憶體模型是JVM虛拟機運作位元組碼的結果。程式員一定要編寫出符合Java語言規範的高品質可維護的、可擴充的源程式,對位元組碼和記憶體模型的了解,可以加深程式員對Java編譯器、Java解釋器,特别是對Java源程式的了解。即加深對源程式的運作過程,過程的深度和細節的了解,更好的完成源程式的功能和安全。

在Java語言中,内部類是指在一個外部類的内部再定義一個類。内部類可以是靜态static的,也可用public,default,protected和private修飾。(而外部頂級類隻能使用public和default,外部頂級類無靜态static)。而在位元組碼語言中,隻有類的概念,沒有外部類和内部類的概念,類隻能使用public和default進行通路控制。

注意:内部類是一個編譯器現象,JVM虛拟機并不知道内部類與正常類有什麼不同。對于源檔案中一個名為Outer的外部類和其内部定義的名為Inner的内部類,編譯成功後會出現Outer.class和Outer$Inner.class兩個位元組碼檔案以及檔案中的Outer和$Inner兩個類,編譯器将會把内部類翻譯成用$(美元符号)分隔外部類名與内部類名的獨立的正常類名,而虛拟機則對此一無所知,虛拟機在運作的時候,也是把Outer$Inner作為一種正常類來處理的。是以内部類的成員變量/方法名可以和外部類的相同。

Java内部類文法描述的是内部類和外部類的關系,而與外部類的父類無關(因為編譯器生成的位元組碼程式必須嚴格完成Java源程式的功能和安全,要修改外部類,而外部類的父類可能是第三方類,是無法修改的)。

使用外部類的主要原因:

1、内部類一般隻為其外部類使用。

2、内部類方法可以通路該内部類定義所在的外部類中的資料,包括私有資料。内部類提供了某種進入外部類的窗戶。

3、 安全)通路控制:内部類可以對同一個包中的其它類隐藏起來。

4、 命名控制:盡管内部類重名,但是它們的外部類不重名,它們編譯後的類名是一個合成名。是以可以避免内部類重名和多義。

5、 當定義一個事件監聽器和其它回調函數時,可使用匿名内部類。

6、 也是最吸引人的原因,每個内部類都能獨立地繼承一個接口,而無論外部類是否已經繼承了某個接口。是以,内部類使多重繼承的解決方案變得更加完整。

内部類辨別符

每個類會産生一個.class檔案,檔案名即為類名。同樣,内部類也會産生這麼一個.class檔案,但是它的名稱卻不是内部類的類名,而是有着嚴格的限制:外圍類的名字,加上$,再加上内部類名字。(後面會進行反編譯,到時候就能看明白了)

全部的内部類總共有六種:

【Java核心技術卷】深入了解Java的内部類

我們首先介紹成員内部類:

成員内部類(非靜态内部類,member inner class)

在外部類的内部,定義的非靜态的内部類,叫成員内部類。

按照Java語言文法規定:

【Java核心技術卷】深入了解Java的内部類

a) 外部類的所有執行個體成員對内部類可見。成員内部類的執行個體對象,有外部類的執行個體對象的引用,是以可以通路外部類執行個體的所有成員(包括私有的)。

b) 外部類的靜态成員對内部類可見。成員内部類可以通路外部類的所有靜态成員(包括私有的)。

c) 外部類對内部類可見。在内部類中可以new生成外部類的執行個體對象。

【Java核心技術卷】深入了解Java的内部類

d) 内部類對外部類可見。因為編譯後的内部類至少是包内,其構造器至少是包内,是以在外部類中可以new生成内部類的執行個體對象。

外部類按正常的類通路方式使用内部類,唯一的差别是外部類可以通路成員内部類的所有方法與屬性,包括私有方法與屬性。

$其他文法規定$

e) 成員内部類可以使用public、protected或private通路修飾符進行通路控制,内部類能夠隐藏起來,不為同一包的其它類通路。成員内部類一般當做成員變量設定為private。

f) 成員内部類是非靜态的。是以在成員内部類中,不能定義靜态字段、靜态方法和靜态内部類,因為成員内部類需要先建立外部類的執行個體對象,才能建立自己的對象;但是可以定義非靜态字段、非靜态方法和非靜态内部類。

Java内部類的執行個體對象有一個隐式引用,它引用了建立該内部類執行個體對象的外部類的執行個體對象。通過這個指針可以通路外部類的執行個體對象的全部狀态(包括私有的)。

這個結合了後面的例子 :

成員内部類的執行個體對象是依附外部類的執行個體對象而存在的,也就是說,如果要建立成員内部類的執行個體對象,前提是必須存在一個外部類的執行個體對象。即在沒有外部類的執行個體對象時,是不能建立内部類的執行個體對象的. 在建立CowLeg内部類的執行個體對象之前,必須先建立Cow外部類的執行個體對象。

CowLeg類的執行個體對象是在Cow類的方法中建立的。由于CowLeg類是私有的,隻有Cow類的方法才能建立CowLeg類的執行個體對象。如果CowLeg類是公有的,在Cow類的内、外部均可以編寫代碼:Cow cow = new Cow(); CowLeg cowLeg = cow.new CowLeg();

局部内部類既可以通路自身的資料域,也可以通路建立它的外部類的執行個體對象的所有資料域. weight可以通路外部類的執行個體對象的私有資料域。

成員内部類的特殊Java文法規則:

1) OuterClass.this

外部類實力引用的文法格式:OuterClass.this

例如:在内部類執行個體對象的方法中引用外部類執行個體對象的私有資料域

System.out.println("本牛腿所在奶牛重:" + Cow.this.weight);

2) outerObject.new InnerClass(construction parameters)

内部類執行個體對象的構造器文法格式:

outerObject.new InnerClass(construction parameters)

例如:在外部類執行個體對象的方法中建立部類執行個體對象

CowLeg cl = this.new CowLeg(1.12, "黑白相間");

3) OuterClass.InnerClass

例如:如果内部類在外部類作用域之外是可見的,則可以這樣引用内部類。

Cow.CowLeg //如果CowLeg類在Cow類的外部是可見的

public class Cow {
    private double weight;

    // 外部類的兩個重載的構造器
    public Cow() {
    }

    public Cow(double weight) {
        this.weight = weight;
    }

    // 定義一個成員内部類
    private class CowLeg {
        // 成員内部類的兩個執行個體變量
        private double length;
        private String color;
        // 由于成員内部類的執行個體的建立依賴于外部類的執行個體的建立,是以成員内部類不可以包含靜态成員
        //private static int age;


        // 成員内部類的兩個重載的構造方法
        public CowLeg() {
        }

        public CowLeg(double length, String color) {
            this.length = length;
            this.color = color;
        }

        // length、color的setter和getter方法
        public void setLength(double length) {
            this.length = length;
        }

        public double getLength() {
            return this.length;
        }

        public void setColor(String color) {
            this.color = color;
        }

        public String getColor() {
            return this.color;
        }

        // 成員内部類的方法
        public void info() {
            System.out.println("目前牛腿顔色是:" + color + ", 高:" + length);
            // 直接通路外部類的private修飾的成員變量
            //weight引用了建立内部類執行個體對象的外部類的執行個體對象的私有資料域。
            System.out.println("本牛腿所在奶牛重:" + weight);
            //如果不寫Cow.this.,編譯器将自動添加
            //System.out.println("本牛腿所在奶牛重:" + Cow.this.weight);
        }
    }

    public void test() {
        //CowLeg類的執行個體對象是在Cow類的執行個體方法中建立的。
        //是以,在建立CowLeg内部類的執行個體對象之前,必先建立Cow外部類的執行個體對象。
        CowLeg cl = new CowLeg(1.12, "黑白相間");
        //如果不寫this.編譯器将自動添加。
        //CowLeg cl = this.new CowLeg(1.12, "黑白相間");
        cl.info();
    }

    public static void main(String[] args) {
        Cow cow = new Cow(378.9);
        cow.test();
        //CowLeg cl = cow.new CowLeg(1.12, "黑白相間");
        //System.out.println("本牛腿所在顔色:" +cl.color);    }
        //被翻譯為以下位元組碼,說明外部類可以通路成員内部類的私有成員。
        // System.out.println("本牛腿所在顔色:" + CowLeg.access$100(cl));
    }
}
           

結果:

【Java核心技術卷】深入了解Java的内部類

反編譯:

編譯器編譯後的位元組碼類檔案:
1.    Cow.class
//
//編譯器在外部類添加了靜态方法 Cow.access$0(Cow arg0)。它将傳回作為參數傳遞給它的對象的私有域weight。
//如果内部類不通路外部類的私有字段,将不會在外部類中添加靜态方法Cow.access$0(Cow arg0)。

public class Cow {
    private double weight;

    public Cow() {
    }

    public Cow(double weight) {
        this.weight = weight;
    }

    public void test() {
        //編譯器将CowLeg cl = new CowLeg(1.12, "黑白相間");語句編譯為
        CowLeg cl = new CowLeg(this, 1.12D, "黑白相間");
        cl.info();
    }

    //編譯器在外部類添加了靜态方法
    static double access$0(Cow arg0){
        return arg0.weight;
    }

    public static void main(String[] args) {
        Cow cow = new Cow(378.9D);
        cow.test();
    }
}           

2. Cow$CowLeg.class

//編譯器為了在内部類的執行個體中引用外部類的執行個體對象,必添加一個附加的執行個體域Cow this$0(this$0名字是由編譯器合成的,在自編寫的代碼中不應該引用它,因為合成名稱可能不同)。
//另外,編譯器修改了所有的内部類的構造器,添加了一個引用外部類執行個體的參數Cow arg0。
//不管内部類是否通路外部類,内部類的構造器是一樣的,均有Cow arg0參數。

class Cow$CowLeg {
    private double length;
    private String color;
    //編譯器必添加一個附加的執行個體域Cow this$0
    final Cow this$0;

    //編譯器在内部類的構造方法中,必添加一個引用外部類執行個體的形參Cow arg0
    public Cow$CowLeg(Cow arg0) {
        this.this$0 = arg0;
    }

    public Cow$CowLeg(Cow arg0, double length, String color) {
        this.this$0 = arg0;
        this.length = length;
        this.color = color;
    }

    public void setLength(double length) {
        this.length = length;
    }

    public double getLength() {
        return this.length;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public String getColor() {
        return this.color;
    }

    public void info() {
        System.out.println("目前牛腿顔色是:" + this.color + ", 高:" + this.length);
        System.out.println("本牛腿所在奶牛重:" + Cow.access$0(this.this$0));
    }
}           

是不是一下子清晰明白了!

看一下它的記憶體對象模型

【Java核心技術卷】深入了解Java的内部類

附注:成員内部類為什麼不能定義靜态的屬性或者靜态方法

//根據成員内部類的定義:
//1、首先生成外部類的執行個體對象
//2、然後生成綁定到外部類執行個體對象的成員内部類執行個體對象
//外部類執行個體對象的生成一定要先于成員内部類執行個體對象的生成

public class InnerClassDemo {
    //對于final修飾的成員變量,說明該變量是隻讀變量,隻能進行一次指派操作,隻可能存在一下兩種指派情況,在這兩個地方我們必須給它們賦初始值。
    //1)聲明該成員變量時指派:
    //    a) 如果表達式的值是常量表達式(傳回基本類型資料),則編譯器編譯時就計算出該表達式的值,知道該值。那麼編譯器就可能會在編譯時期,優化程式。
    //    b) 如果不是常量表達式(傳回引用類型資料),則編譯器編譯時,就不知道該值。
    //2)在構造方法中指派,運作時指派,則編譯器編譯時,就不知道該值。

    //執行個體成員變量被預設初始化為0
    int x;

    //靜态成員變量必須被顯式初始化,要麼在聲明時指派初始化,要麼在靜态塊中初始化。否則,文法錯誤。
    static final int i;
    static final int j = 2;
    //靜态塊,當類被JVM加載到記憶體時,靜态塊的靜态代碼執行。
    static{
        i = 1;
        System.out.println("i = " + i);
        System.out.println("j = " + j);
    }

    public static void main(String[] args) {

        System.out.println("welcom!");
    }


class InnerClass{

        //1、在成員内部類中,隻有編譯器在編譯的時候指派号右邊是常量表達式(編譯時,可以計算出表達式的基本類型值),
        //   左邊是隻讀靜态常量的情況才可以存在靜态隻讀常量
        //   然後編譯器把他當做編譯期常量來使用,其實說白了就是和這個外部類的執行個體無關。
        //static final int i = 50;  
        //static final String str =  "s"; 

        //2、以下均不可以:
        //2.1 雖然也為static final隻讀靜态變量,但是是在構造方法中運作時指派,編譯時并不知道其值。
        //static final int i;  
        //static final String str;  
        //2.2 雖然也為static final隻讀靜态變量,但是指派号右邊不是常量表達式(傳回引用類型資料),編譯時并不知道其引用的執行個體值。
        //static final String str = new String(""); 
        //2.3 隻是靜态變量,運作時可以動态指派
        //static  int i = 50;  

        //3、但是沒有外部類執行個體此内部類不需要外部類執行個體就初始化了變量,與了成員内部類的定義相悖。
        //   由于3不可以,是以2也不可以。因為編譯器無法差別2和3這兩種情況。  
        //static InnerClass innerClass = new InnerClass(); 

        //4、靜态方法中,但是沒有外部類執行個體  
        //static void method() { InnerClass innerClass = new  InnerClass(); };
    }
}             

其實内部類并不是完全不能出現static 這樣的修飾的,隻要符合第一種情況的就是可以的。

編譯器是無法差別第二種,第三種情況的,第三種的情況肯定是不行的與内部成員類的定義相駁,是以第二種情況在文法上也被禁止了。

第三種情況,根據初始化的流程我們知道,在類加載的時候,static變量就必須被顯式初始化,那麼我們InnerClass成員内部類的執行個體對象在沒有InnerClassDemo外部類的執行個體對象的時候便生成了。這樣這個成員内部類就脫離了外部類的掌控,不需要外部類的對象就可以生成内部類的對象,這與成員内部類的定義就相駁了,因為我們知道成員内部類的對象必須是先有外部類的對象才能建立,成員内部類的對象 脫離了其外部類的對象 就不會存在,并且是綁定在一起的,是以成員内部類不可以定義靜态變量。

下面介紹第二個

靜态内部類(static inner class / Static Nested Class)

将成員内部類的使用再深入限制一步,假如内部類的執行個體對象不需要引用外部類的執行個體對象,隻是将一個類隐藏在另外一個類的内部,可将該内部類靜态化。

在外部類的内部,定義的靜态的内部類,叫靜态内部類。(或叫嵌套類)

提示:靜态内部類在實際工作中用的并不是很多。

【Java核心技術卷】深入了解Java的内部類

a) 外部類的靜态成員對内部類可見。靜态内部類可以通路外部類的所有靜态成員(包括私有的)。

b) 外部類對内部類可見。在内部類中可以new生成外部類的執行個體對象。

c) 外部類的執行個體成員對内部類不可見。靜态内部類的執行個體對象,沒有外部類的執行個體對象的引用,是以不可以通路外部類執行個體。

【Java核心技術卷】深入了解Java的内部類

其它文法:

e) 靜态内部類可以使用public、protected或private通路修飾符進行通路控制,内部類能夠隐藏起來,不為同一包的其它類通路。一般當做成員變量設定為private。

f) 靜态内部類是靜态的。是以在靜态内部類中,可以定義靜态字段、靜态方法和靜态内部類;也可以定義非靜态字段、非靜态方法和非靜态内部類。

與成員内部類相比,靜态内部類沒有編譯器自動添加的執行個體字段和構造器參數。即靜态内部類的執行個體是不可以通路外部類的執行個體成員。

靜态内部類在實際工作中用的并不是很多。如在程式測試的時候,為了避免在各個Java源檔案中書寫主方法的代碼,可以将主方法寫入到靜态内部類中,以減少代碼的書寫量,讓代碼更加的簡潔。

public class StaticInnerClassDemo {
    private int prop1 = 5;
    private static int prop2 = 9;

    static class StaticInnerClass {
        // 靜态内部類裡可以包含靜态成員
        // private static int age;
        // private int number = 28;

        public void accessOuterProp() {
            // 下面代碼出現錯誤:
            // 靜态内部類無法直接通路外部類的執行個體變量
            //System.out.println(prop1);
            // 下面代碼正常
            System.out.println(prop2);
        }
    }

    public static void main(String[] args) {
        StaticInnerClass staticInnerClass = new StaticInnerClass();
        staticInnerClass.accessOuterProp();
        //System.out.println(staticInnerClass.age);
//被翻譯為以下位元組碼,說明外部類可以通路靜态内部類的私有成員。
//System.out.println(StaticInnerClass.access$100(staticInnerClass));
    }
}           

編譯器編譯後的位元組碼類檔案:

1. StaticInnerClassDemo.class

//編譯器在外部類添加了靜态方法StaticInnerClassDemo.access$0(),它将傳回私有的靜态域prop2。通過靜态方法通路私有的靜态字段。
//如果内部類不通路外部類的靜态私有成員,将不會添加靜态方法StaticInnerClassDemo. access$0()。

public class StaticInnerClassDemo {
    private int prop1 = 5;
    private static int prop2 = 9;

    static int access$0(){
        return StaticInnerClassDemo.prop2;
    }

    public static void main(String[] args) {
        StaticInnerClass staticInnerClass = new StaticInnerClass();
        staticInnerClass.accessOuterProp();
    }
}           

2. StaticInnerClassDemo$StaticInnerClass.class

class StaticInnerClassDemo$StaticInnerClass {

    public void accessOuterProp() {
        System.out.println(StaticInnerClassDemo.access$0());
    }
}           

靜态類的記憶體對象模型:

【Java核心技術卷】深入了解Java的内部類

附錄:java 内部類和靜态内部類的差別

1、靜态内部類可以有靜态成員(方法,屬性),而非靜态内部類則不能有靜态成員(方法,屬性)。

2、靜态内部類隻能夠通路外部類的靜态成員,而非靜态内部類則可以通路外部類的所有成員(方法,屬性)。

3、執行個體化一個非靜态的内部類的方法:

  • a.先生成一個外部類對象執行個體

    OutClassTest oc1 = new OutClassTest();

  • b.通過外部類的對象執行個體生成内部類對象

    OutClassTest.InnerClass no_static_inner = oc1.new InnerClass();

4、 執行個體化一個靜态内部類的方法:

  • a.不依賴于外部類的執行個體,直接執行個體化内部類對象
    OutClassTest.InnerStaticClass inner =            
  1. OutClassTest.InnerStaticClass();
  • b.調用内部靜态類的方法或靜态變量,通過類名直接調用
    OutClassTest.InnerStaticClass.static_value
      OutClassTest.InnerStaticClass.getMessage()           

接着介紹另外一種内部類:

局部内部類(local inner class)

在外部類的方法中,定義的非靜态的命名的内部類,叫局部内部類。(因為内部類可以通路外部類方法的形參和局部變量而得此名)

可以分為:在外部類的執行個體方法内部的局部内部類和在外部類的靜态方法内部的局部内部類。

提示:在實際開發中很少使用局部内部類,隻是因為局部内部類的作用域很小,隻能在目前方法中使用。

1.在外部類的執行個體方法内部的局部内部類。

【Java核心技術卷】深入了解Java的内部類

a) 執行個體方法的形參對局部内部類可見。局部内部類的執行個體對象,可以有外部類的執行個體方法的形參的字段,可以通路外部類執行個體方法的形參。這些形參在JDK8之前必須被聲明為final,但在JDK8中就不需要了。

b) 執行個體方法的局部變量對局部内部類可見。局部内部類的執行個體對象,可以有外部類的執行個體方法的局部變量的字段,可以通路外部類執行個體方法的局部變量。這些局部變量在JDK8之前必須被聲明為final,但在JDK8中就不需要了。

【Java核心技術卷】深入了解Java的内部類

a) 外部類的執行個體成員對局部内部類可見。局部内部類的執行個體對象,有外部類的執行個體對象的引用,是以可以通路外部類執行個體的所有成員(包括私有的)。

b) 外部類的靜态成員對局部内部類可見。局部内部類可以通路外部類的所有靜态成員(包括私有的)。

c) 外部類對局部内部類可見。在内部類中可以new生成外部類的執行個體對象。

【Java核心技術卷】深入了解Java的内部類

d) 局部内部類對外部類的執行個體方法可見。因為編譯後的内部類至少是包内,其構造器至少是包内,是以在外部類的執行個體方法中可以new生成局部内部類的執行個體對象。

【Java核心技術卷】深入了解Java的内部類

e) 局部内部類不能使用public、protected或private通路修飾符進行通路控制,它的作用域被限定在聲明該局部内部類的方法塊中,是以在外部類的方法中不可以new生成局部内部類的執行個體對象。除了局部内部類所在的外部類方法,沒有任何方法知道内部類的存在。

其他文法規定:

f) 局部内部類是非靜态的。是以在局部内部類中,不能定義靜态字段、靜态方法和靜态内部類;但是可以定義非靜态字段、非靜态方法和非靜态内部類。

class InstanceLocalOut {
    private int age = 12;

    // 
//final形參、final局部變量,是編譯器的文法,位元組碼中并不存在。
//使用final可以使得形參、局部變量與在局部内部類執行個體建立的字段拷貝保持一緻。
//1. 在JDK8之前的版本,必需要寫final修飾符
//   1)如果寫上final形參,告知編譯器,形參在方法内部是不能改變的;
//   2)如果寫上final局部變量,告知編譯器,局部變量在方法内部隻能指派一次,
//   以後不能改變的;
//2. 在JDK8及其以後的版本,不需要再寫final修飾符了(寫上也無妨),由編譯器自動判斷
//   1)如果局部内部類使用了形參,
//      則編譯器在編譯時自動判斷形參在方法内部是不能改變的;
//   2) 如果局部内部類使用了方法内部的局部變量,
//      則編譯器在編譯時自動判斷局部變量在方法内部隻能指派一次,以後不能改變的。
    public void Print(final int x) {
        final int m = 8;

// 在執行個體方法中定義一個局部内部類
        class InstanceLocalIn {
            // 局部内部類的執行個體方法
            public void inPrint() {
                // 直接通路外部類的private修飾的成員變量age
                System.out.println(age);
                // 直接通路外部類執行個體方法的形參x
                System.out.println(x);
                // 直接通路外部類執行個體方法的局部變量m
                System.out.println(m);
            }
        }

// InstanceLocalIn類的執行個體對象是在InstanceLocalOut類的執行個體方法中建立的。
//是以,在建立InstanceLocalIn局部内部類的執行個體對象之前,必先建立InstanceLocalOut外部類的執行個體對象(外部類Print方法的隐藏形參this)。            
        InstanceLocalIn instanceLocalIn = new InstanceLocalIn();
        instanceLocalIn.inPrint();
    }
}

public class InstanceLocalInnerClass {
    public static void main(String[] args) {
        InstanceLocalOut out = new InstanceLocalOut();
        out.Print(3);
    }
}
           
//外部類
//編譯器在外部類添加了靜态方法 InstanceLocalOut.access$0(InstanceLocalOut arg0)。它将傳回 作為參數傳遞給它的對象 的私有域age。
//如果内部類不通路外部類的私有字段,将不會在外部類中添加靜态方法InstanceLocalOut.access$0(InstanceLocalOut arg0)。
--------------------------------------------------------------------
import InstanceLocalOut.1InstanceLocalIn;

class InstanceLocalOut {
    private int age = 12;

    public void Print(int x) {
        byte m = 8;
//編譯器将InstanceLocalIn instanceLocalIn = new InstanceLocalIn();語句編譯為
        1InstanceLocalIn instanceLocalIn = new 1InstanceLocalIn(this, x, m);
        instanceLocalIn.inPrint();
    }

    //編譯器在外部類添加了靜态方法
    static double access$0(InstanceLocalOut arg0){
        return arg0.age;
    }

}

//外部類的執行個體方法中的局部内部類
//編譯器為了在内部類的執行個體中引用外部類的執行個體對象,必添加一個附加的執行個體域InstanceLocalOut this$0(this$0名字是由編譯器合成的,在自編寫的代碼中不應該引用它)。
//如果内部類通路外部類的執行個體方法中的形參x,編譯器将修改内部類,添加一個附加的執行個體域參數int val$x。否則,将不會添加附加的執行個體域。
//如果内部類通路外部類的執行個體方法中的局部變量m,編譯器将修改内部類,添加一個附加的執行個體域參數int val$m。否則,将不會添加附加的執行個體域。

class InstanceLocalOut$1InstanceLocalIn {
    final InstanceLocalOut this$0;
    private final int val$x;
    private final int val$m;

    InstanceLocalOut$1InstanceLocalIn(InstanceLocalOut arg0, int arg1, int arg2) {
        this.this$0 = arg0;
        this.val$x = arg1;
        this.val$m = arg2;
    }

    public void inPrint() {
        System.out.println(InstanceLocalOut.access$0(this.this$0));
        System.out.println(this.val$x);
        System.out.println(this.val$m);
    }
}           

外部類的記憶體執行模型:

【Java核心技術卷】深入了解Java的内部類

在外部類的靜态方法中的局部内部類

靜态方法中的局部内部類除了不能通路外部類的執行個體外(因為它沒有外部類的執行個體對象的引用),與執行個體方法中的局部内部類相同。

【Java核心技術卷】深入了解Java的内部類
【Java核心技術卷】深入了解Java的内部類

a) 外部類的靜态成員對局部内部類可見。局部内部類可以通路外部類的所有靜态成員(包括私有的)。

b) 外部類對局部内部類可見。在局部内部類中可以new生成外部類的執行個體對象。

c) 外部類的執行個體成員對内部類不可見。局部内部類的執行個體對象,沒有外部類的執行個體對象的引用,是以不可以通路外部類執行個體的所有成員。

【Java核心技術卷】深入了解Java的内部類

d) 局部内部類對外部類的靜态方法可見。因為編譯後的内部類至少是包内,其構造器至少是包内,是以在外部類的靜态方法中可以new生成内部類的執行個體對象。

【Java核心技術卷】深入了解Java的内部類

e) 局部内部類不能使用public、protected或private通路修飾符進行通路控制,它的作用域被限定在聲明該局部内部類的方法塊中。

class StaticLocalOut {
    private int age = 12;

    static public void print(int x) {
            final int m = 8;
        class StaticLocalIn {
            public void inPrint() {
               //靜态方法中的局部内部類不能通路外部類的執行個體
               //System.out.println(age);
               System.out.println(x);
               System.out.println(m);
            }
        }
        StaticLocalIn staticLocalIn = new StaticLocalIn();
        staticLocalIn.inPrint();
    }
}

public class StaticLocalInnerClass {
    public static void main(String[] args) {
        StaticLocalOut.Print(3);
    }
}
           
//外部類
import StaticLocalOut.1StaticLocalIn;

class StaticLocalOut {
    private int age = 12;

    public static void print(int x) {
      byte m = 8;
      1StaticLocalIn staticLocalIn = new 1StaticLocalIn(x, m);
      staticLocalIn.inPrint();
   }
}
//外部類的靜态方法中的局部内部類
class StaticLocalOut$1StaticLocalIn {
    StaticLocalOut$1StaticLocalIn(int arg0, int arg1) {
        this.val$x = arg0;
        this.val$m = arg1;
    }

    public void inPrint() {
        System.out.println(this.val$x);
        System.out.println(this.val$m);
    }
}           

外部類的靜态方法内部的局部内部類的記憶體模型

【Java核心技術卷】深入了解Java的内部類

匿名内部類(anonymous inner class )

将局部内部類的使用再深入一步,假如隻建立這個類的一個對象,就不必命名了。從使用上講,匿名内部類和局部内部類的差別是一個是匿名的另一個是命名的,其它均相同。(匿名的含義是由編譯器自動給内部類起一個内部名稱)

在外部類的方法中,定義的非靜态的沒有類名的内部類,叫匿名内部類。

匿名内部類适合隻需要使用一次的類,當建立一個匿名内部類時會立即建立該類的一個執行個體對象,匿名類不能重複使用。

可以分為:在外部類的執行個體方法内部的匿名内部類,

在外部類的靜态方法内部的匿名内部類。

提示:最好使用lambda表達式來代替匿名内部類。

1.在外部類的執行個體方法内部的匿名内部類。

【Java核心技術卷】深入了解Java的内部類

a) 執行個體方法的形參對匿名内部類可見。匿名内部類的執行個體對象,可以有外部類的執行個體方法的形參的字段,可以通路外部類執行個體方法的形參。這些形參在JDK8之前必須被聲明為final,但在JDK8中就不需要了。

b) 執行個體方法的局部變量對匿名内部類可見。匿名内部類的執行個體對象,可以有外部類的執行個體方法的局部變量的字段,可以通路外部類執行個體方法的局部變量。這些局部變量在JDK8之前必須被聲明為final,但在JDK8中就不需要了。

【Java核心技術卷】深入了解Java的内部類

c) 外部類的執行個體成員對匿名内部類可見。匿名内部類的執行個體對象,有外部類的執行個體對象的引用,是以可以通路外部類執行個體的所有成員(包括私有的)。

d) 外部類的靜态成員對匿名内部類可見。匿名内部類可以通路外部類的所有靜态成員(包括私有的)。

e) 外部類對局部内部類可見。在匿名内部類中可以new生成外部類的執行個體對象。

【Java核心技術卷】深入了解Java的内部類

f) 匿名内部類對外部類該執行個體方法可見。因為編譯後的内部類至少是包内,其構造器至少是包内,是以在外部類中執行個體方法可以new生成内部類的執行個體對象。

【Java核心技術卷】深入了解Java的内部類

g) 匿名内部類不能使用public、protected或private通路修飾符進行通路控制,它的作用域被限定在聲明該匿名内部類的方法塊中。

其它文法規定:

h) 由于構造器的名字必須與類名相同,而匿名類無類名,是以匿名内部類不能有構造器。取而代之的是将構造器參數傳遞給父類構造器。

i) 匿名内部類是非靜态的。是以在匿名内部類中,不能定義靜态字段、靜态方法和靜态内部類;但是可以定義非靜态字段、非靜态方法和非靜态内部類。

class FatherofAnonIn {
    protected int a;
    FatherofAnonIn(int a){
        this.a = a;
    }
}

class InstanceAnonOut {
    private int age = 12;

    public void Print(int x) {
        int m = 8;

//1. 建立FatherofAnonIn類的派生匿名類
//2. 匿名内部類不能有定義的執行個體構造器,将執行個體構造器參數傳遞給父類執行個體構造器進行初始化。
//3. 生成匿名内部類的執行個體對象
//4. 通過匿名内部類的執行個體對象調用匿名類的方法
        (new FatherofAnonIn(10){
            public void inPrint() {
                System.out.println(age);
                System.out.println(x);
                System.out.println(m);
                System.out.println(a);
            }
        }).inPrint();
    }
}

public class InstanceAnonInnerClass {
    public static void main(String[] args) {
        InstanceAnonOut out = new InstanceAnonOut();
        out.Print(3);
    }
}
           

編譯後的位元組碼:

//外部類
import InstanceAnonOut.1;

class InstanceAnonOut {
    private int age = 12;

    public void Print(int x) {
      byte m = 8;
      (new 1(this, 10, x, m)).inPrint();
   }

//編譯器在外部類添加了靜态方法
static int access$0(InstanceAnonOut arg0){
        return arg0.age;
}

}

//匿名内部類
class InstanceAnonOut$1 extends FatherofAnonIn {
    InstanceAnonOut$1(InstanceAnonOut arg0, int $anonymous0, int arg2, int arg3) {
        super($anonymous0);
        this.this$0 = arg0;
        this.val$x = arg2;
        this.val$m = arg3;
    }

    public void inPrint() {
        System.out.println(InstanceAnonOut.access$0(this.this$0));
        System.out.println(this.val$x);
        System.out.println(this.val$m);
        System.out.println(this.a);
    }
}           

外部類的執行個體方法内部的匿名内部類的記憶體模型

【Java核心技術卷】深入了解Java的内部類

在外部類的靜态方法中的匿名内部類

靜态方法中的匿名内部類除了不能通路外部類的執行個體外(因為它沒有外部類的執行個體對象的引用),與執行個體方法中的匿名内部類相同。

【Java核心技術卷】深入了解Java的内部類

a) 靜态方法的形參對匿名内部類可見。匿名内部類的執行個體對象,可以有外部類的靜态方法的形參的字段,可以通路外部類靜态方法的形參。這些形參在JDK8之前必須被聲明為final,但在JDK8中就不需要了。

b) 靜态方法的局部變量對匿名内部類可見。匿名内部類的執行個體對象,可以有外部類的靜态方法的局部變量的字段,可以通路外部類靜态方法的局部變量。這些局部變量在JDK8之前必須被聲明為final,但在JDK8中就不需要了。

【Java核心技術卷】深入了解Java的内部類

c) 外部類的靜态成員對匿名内部類可見。匿名内部類可以通路外部類的所有靜态成員(包括私有的)。

d) 外部類對匿名局部内部類可見。在局部内部類中可以new生成外部類的執行個體對象。

e) 外部類的執行個體成員對匿名内部類不可見。匿名内部類的執行個體對象,沒有外部類的執行個體對象的引用,是以不可以通路外部類執行個體的所有成員(包括私有的)。

【Java核心技術卷】深入了解Java的内部類

f) 内部類對外部類的靜态方法可見。因為編譯後的内部類至少是包内,其構造器至少是包内,是以在外部類的靜态方法中可以new生成内部類的執行個體對象。

【Java核心技術卷】深入了解Java的内部類

h) 匿名内部類不能有構造方法。因為匿名類無類名,是以無法寫構造方法。取而代之的是将構造器參數傳遞給超類構造器。

//源程式:
class FatherofAnonIn {
    protected int a;
    FatherofAnonIn(int a){
        this.a = a;
    }
}

class StaticAnonOut {
    private int age = 12;

    public static void print(int x) {
        int m = 8;
        
        (new FatherofAnonIn(10){
            public void inPrint() {
               //System.out.println(age);
               System.out.println(x);
               System.out.println(m);
               System.out.println(a);
            }
        }).inPrint();
     }
}

public class StaticAnonInnerClass {
    public static void main(String[] args) {
        StaticAnonOut.Print(3);
    }
}           
//外部類
import StaticAnonOut.1;

class StaticAnonOut {
    private int age = 12;

    public static void print(int x) {
      byte m = 8;
      (new 1(10, x, m)).inPrint();
   }
}

//匿名内部類
class StaticAnonOut$1 extends FatherofAnonIn {
    StaticAnonOut$1(int $anonymous0, int arg1, int arg2) {
        super($anonymous0);
        this.val$x = arg1;
        this.val$m = arg2;
    }

    public void inPrint() {
        System.out.println(this.val$x);
        System.out.println(this.val$m);
        System.out.println(this.a);
    }
}           

外部類的靜态方法内部的匿名内部類的記憶體模型

【Java核心技術卷】深入了解Java的内部類