(1) 在類中定義一個類(私有內部類,靜態內部類)
(2) 在方法中定義一個類(局部內部類,匿名內部類)
1、揭露類中內部類
????? 我們首先看看類中內部類的兩個特點:
???? (1) 在外部類的作用范圍內可以任意創建內部類對象,即使內部類是私有的(私有內部類)。即內部類對包圍它的外部類可見。
- class?Outer{ ??
- ?????//創建私有內部類對象 ??
- ?????public?Inner?in=new?Inner(); ??
- ?????//私有內部類 ??
- ?????private?class?Inner{ ??
- ??????????... ??
- ?????} ??
- }???
(2) 在內部類中可以訪問其外部類的所有域,即使是私有域。即外部類對內部類可見。
- class?Outer{ ??
- ???????//外部類私有數據域 ??
- ???????private?int?data=0; ??
- ???????//內部類 ??
- ???????class?Inner{ ??
- ???????????void?print(){ ??
- ?????????????????//內部類訪問外部私有數據域 ??
- ?????????????????System.out.println(data); ??
- ???????????}? ??
- ???????} ??
- }??
?????? 問題來了:上面兩個特點到底如何辦到的呢?內部類的"內部"到底發生了什么?
????? 其實,內部類是Java編譯器一手操辦的。虛擬機并不知道內部類與常規類有什么不同。編譯器是如何瞞住虛擬機的呢?
???? 對內部類進行編譯后發現有兩個class文件:Outer.class和Outer$Inner.class。這說明內部類Inner仍然被編譯成一個獨立的類(Outer$Inner.class),而不是Outer類的某一個域。虛擬機運行的時候,也是把Inner作為一種常規類來處理的。
??????? 但問題來了,即然是兩個常規類,為什么他們之間可以互相訪問私有域那(最開始提到的兩個內部類特點)?這就要問問編譯器到底把這兩個類編譯成什么東西了。
??????? 我們利用reflect反射機制來探查了一下內部類編譯后的情況(關于探查類內部機制的代碼提供在下面的附件里Reflect.java)。
??????? (1)、編譯代碼2生成Outer$Inner.class 文件后使用ReflectUtil.reflect("Outer$Inner")對內部類Inner進行反射。運行結果發現了三個隱含的成分:??????????
- { ??
- ????????Outer$Inner(Outer,Outer$Inner);??//包可見構造器 ??
- ????????private?Outer$Inner(Outer);???//私有構造器將設置this$0域 ??
- ????????final?Outer?this$0;???//外部類實例域this$0 ??
- }??
?
????? 好了,現在我們可以解釋上面的第一個內部類特點了:為什么外部類可以創建內部類的對象?
???? 首先編譯器將外、內部類編譯后放在同一個包中。
??????? 然后在內部類中附加一個包可見構造器。
??????? 這樣,虛擬機運行Outer類中Inner in=new Inner();實際上調用的是包可見構造: new Outer$Inner(this,null)。
??????? 因此即使是private內部類,也會通過隱含的包可見構造器成功的獲得私有內部類的構造權限。
???? (2)、編譯代碼3生成Outer.class文件,然后使用ReflectUtil.reflect("Outer")對外部類Outer進行反射。 運行結果發現一個隱含成分如下:
- class?Outer ??
- { ??
- ??????????static?int?access$0(Outer);??//靜態方法,返回值是外部類私有域?data?的值。 ??
- }??
????? 現在可以解釋第二個特點了:為什么內部類可以引用外部類的私有域?
????????原因的關鍵就在編譯器在外圍類中添加了靜態方法access$0。 它將返回值作為參數傳遞給他的對象域data。這樣內部類Inner中的打印語句:
???????????????????? System.out.println(data);
???????? 實際上運行的時候調用的是:
???????????????? System.out.println(access$0(Outer));
總結一下編譯器對類中內部類做的手腳吧:
(1)? 在內部類中偷偷摸摸的創建了包可見構造器,從而使外部類獲得了創建權限。
(2)? 在外部類中偷偷摸摸的創建了訪問私有變量的靜態方法,從而使內部類獲得了訪問權限。
這樣,類中定義的內部類無論私有,公有,靜態都可以被包圍它的外部類所訪問。
?
2、方法中內部類的特點
????? 方法內部類也有兩個特點
????? (1)? 方法中的內部類沒有訪問修飾符,即方法內部類對包圍它的方法之外的任何東西都不可見。
????? (2)? 方法內部類只能夠訪問該方法中的局部變量,所以也叫局部內部類。而且這些局部變量一定要是final修飾的常量。
- ??????public?void?outMethod(){ ??
- ?????????????final?int?beep=0; ??
- ?????????????class?Inner{ ??
- ???????????????????//使用beep ??
- ?????????????} ??
- ?????????????Inner?in=new?Inner(); ??
- ??????} ??
- }??
????? 這又是為什么呢?
????? (1) 我們首先對Outter類進行反射發現,Outter中再也沒有返回私有域的隱藏方法了。
????? (2) 對Inner類的反射發現,Inner類內部多了一個對beep變量的備份隱藏域:final int val$i;
????? 我們可以這樣解釋Inner類中的這個備份常量域,首先當JVM運行到需要創建Inner對象之后,Outter類已經全部運行完畢,這是垃圾回收機制很有可能釋放掉局部變量beep。那么Inner類到哪去找beep變量呢?
????? 編譯器又出來幫我們解決了這個問題,他在Inner類中創建了一個beep的備份,也就是說即使Ouuter中的beep被回收了,Inner中還有一個備份存在,自然就不怕找不到了。
????? 但是問題又來了。如果Outter中的beep不停的在變化那。那豈不是也要讓備份的beep變量無時無刻的變化。為了保持局部變量與局部內部類中備份域保持一致。編譯器不得不規定死這些局部域必須是常量,一旦賦值不能再發生變化了。
????? 所以為什么局部內部類應用外部方法的域必須是常量域的原因所在了。
Author: orangelizq
email: orangelizq@163.com
|
|