【解惑】領略Java內部類的「內部」

內部類有兩種狀況:
(1) 在類中定義一個類(私有內部類,靜態內部類)
(2) 在方法中定義一個類(局部內部類,匿名內部類)
 
一、私有內部類 —— 在方法之間定義的內部類,非靜態
      咱們首先看看類中內部類的兩個特色:
      (1) 在外部類的做用範圍內能夠任意建立內部類對象,即便內部類是私有的(私有內部類)。即內部類對包圍它的外部類可見。

例如 (實際上,內部類是能夠設置爲public公開的,但通常我是當作成員變量設置爲private) java


//代碼1:內部類對外部類可見 
class Outer{ 
    //建立私有內部類對象 
    public Inner in=new Inner(); 
 
    //私有內部類 
    private class Inner{ 
         ... 
    } 
}



 (2) 在內部類中能夠訪問其外部類的全部域,即便是私有域。即外部類對內部類可見。
例如
//代碼2:外部類對內部類可見 
//(內部類能夠訪問外部類的全部成員變量和方法) 
class Outer{ 
    //外部類私有數據域 
    private int data=0; 
 
    //內部類 
    class Inner{ 
        void print(){ 
            //內部類訪問外部私有數據域 
            System.out.println(data); 
        } 
    } 
}

      問題來了:上面兩個特色到底如何辦到的呢?內部類的"內部"到底發生了什麼? this


       其實,內部類是Java編譯器一手操辦的。虛擬機並不知道內部類與常規類有什麼不一樣。 編譯器是如何瞞住虛擬機的呢?
       對內部類進行編譯後發現有兩個class文件:Outer.class 和Outer$Inner.class 。這說明內部類Inner仍然被編譯成一個獨立的類(Outer$Inner.class),而不是Outer類的某一個域。 虛擬機運行的時候,也是把Inner做爲一種常規類來處理的。
 
      但問題來了,即然是兩個常規類,爲何他們之間能夠互相訪問私有域那(最開始提到的兩個內部類特色)?這就要問問編譯器到底把這兩個類編譯成什麼東西了。
 
        咱們利用reflect反射機制來探查了一下內部類編譯後的狀況(關於探查類內部機制的代碼提供在下面的附件裏Reflect.java)。
        (1)、編譯代碼1生成 Outer$Inner.class 文件後使用 ReflectUtil.reflect("Outer$Inner") 對內部類Inner進行反射。運行結果 發現了三個隱含的成分:
//反編譯代碼
class Outer$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內部類,也會經過隱含的包可見構造器成功的得到私有內部類的構造權限。
      再者,Outer$Inner類中有一個指向外部類Outer的引用this$0,那麼經過這個引用就能夠方便的獲得外部類對象中可見成員。可是Outer類中的private成員是如何訪問到的呢?這就要看看下面Outer.class文件中的祕密了。
 
    (2)、編譯代碼2生成 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(this$0.access$0(Outer));
 
總結一下編譯器對類中內部類作的手腳吧:
(1)  在內部類中偷偷摸摸的建立了包可見構造器,從而使外部類得到了建立權限。
(2)  在外部類中偷偷摸摸的建立了訪問私有變量的靜態方法,從而 使 內部類得到了訪問權限。
這樣,類中定義的內部類不管私有,公有,靜態均可以被包圍它的外部類所訪問。
 
二、靜態內部類  ——  在方法間定義的內部類,靜態
    內部類也有靜態的區別,這就是靜態內部類,咱們來看看代碼:



package hr.test; 
//代碼3:靜態內部類對外部變量的引用 
public class Outer{ 
    private static int i=0; 
 
    //建立靜態內部類對象 
    public Inner in=new Inner(); 
 
    //靜態內部類 
    private static class Inner{ 
        public void print(){ 
            System.out.println(i);   //若是i不是靜態變量,這裏將沒法經過編譯 
        } 
    }  
}



 靜態內部類和私有內部類最大的區別在於,靜態內部類中沒法引用到其外圍類的非靜態成員。這是爲何?咱們仍是來看看靜態內部類Outer$Inner中發生了什麼吧?



//反編譯代碼 
class Outer$Inner 
{ 
    private Outer$Inner(); 
    Outer$Inner(hr.test.Outer$Inner); 
}



與上面私有內部類反編譯1比較發現, 少了一個指向外圍類對象的引用final Outer this$0; 也就是說靜態內部類沒法獲得其外圍類對象的引用,那麼天然也就沒法訪問外圍類的非靜態成員了。 所以,靜態內部類只能訪問其外圍類的靜態成員,除此以外與非靜態內部類沒有任何區別。
 
三、局部內部類 —— 在方法中定義的內部類
      方法內部類也有兩個特色
      (1)   方法中的內部類沒有訪問修飾符, 即方法內部類對包圍它的方法以外的任何東西都不可見。
      (2)   方法內部類只可以訪問該方法中的局部變量,因此也叫局部內部類。並且這些局部變量必定要是final修飾的常量
例如
class Outter{ 
    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變量無時無刻的變化。 爲了保持局部變量與局部內部類中備份域保持一致。 編譯器不得不規定死這些局部域必須是常量,一旦賦值不能再發生變化了。
      因此爲何局部內部類應用外部方法的域必須是常量域的緣由所在了。
 
 
內部類的特色總結
 
(1)  在方法間定義的非靜態內部類: 
       ● 外圍類和內部類可互相訪問本身的私有成員。
       ● 內部類中不能定義靜態成員變量。

(2) 在方法間定義的靜態內部類:
       ● 只能訪問外部類的靜態成員。
 
(3) 在方法中定義的局部內部類:
       ● 該內部類沒有任何的訪問控制權限
       ● 外圍類看不見方法中的局部內部類的,可是局部內部類能夠訪問外圍類的任何成員。
       ● 方法體中能夠訪問局部內部類,可是訪問語句必須在定義局部內部類以後。
       ● 局部內部類只能訪問方法體中的常量,即用final修飾的成員。

(4) 在方法中定義的匿名內部類:
       ● 沒有構造器,取而代之的是將構造器參數傳遞給超類構造器。
相關文章
相關標籤/搜索