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

內部類有兩種狀況:java

(1) 在類中定義一個類(私有內部類,靜態內部類)this

(2) 在方法中定義一個類(局部內部類,匿名內部類)對象

 

一、私有內部類 —— 在方法之間定義的內部類,非靜態編譯器

      咱們首先看看類中內部類的兩個特色:虛擬機

      (1) 在外部類的做用範圍內能夠任意建立內部類對象,即便內部類是私有的(私有內部類)。即內部類對包圍它的外部類可見。編譯

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

 
  1. //代碼1:內部類對外部類可見 
  2. class Outer{ 
  3.     //建立私有內部類對象 
  4.     public Inner in=new Inner(); 
  5.  
  6.     //私有內部類 
  7.     private class Inner{ 
  8.          ... 
  9.     } 

 

      (2) 在內部類中能夠訪問其外部類的全部域,即便是私有域。即外部類對內部類可見。test

例如變量

 
  1. //代碼2:外部類對內部類可見 
  2. //(內部類能夠訪問外部類的全部成員變量和方法) 
  3. class Outer{ 
  4.     //外部類私有數據域 
  5.     private int data=0; 
  6.  
  7.     //內部類 
  8.     class Inner{ 
  9.         void print(){ 
  10.             //內部類訪問外部私有數據域 
  11.             System.out.println(data); 
  12.         } 
  13.     } 

 

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

       其實,內部類是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進行反射。運行結果 發現了三個隱含的成分:

 
  1. //反編譯代碼
  2. class Outer$Inner
  3. {
  4.     Outer$Inner(Outer,Outer$Inner);  //包可見構造器
  5.     private Outer$Inner(Outer);   //私有構造器將設置this$0域
  6.     final Outer this$0;   //外部類實例域this$0
  7. }

    好了,如今咱們能夠解釋上面的第一個內部類特色了: 爲何外部類能夠建立內部類的對象?而且內部類可以方便的引用到外部類對象?

     首先編譯器將外、內部類編譯後放在同一個包中。在內部類中附加一個包可見構造器。這樣, 虛擬機運行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進行反射 。 運行結果 發現一個隱含成分以下:

 
  1. //反編譯代碼 
  2. class Outer 
  3.     static int access$0(Outer);  //靜態方法,返回值是外部類私有域 data 的值 

    如今能夠解釋第二個特色了:爲何內部類能夠引用外部類的私有域?

    緣由的關鍵就在編譯器在外圍類中添加了靜態方法access$0。 它將返回值做爲參數傳遞給他的對象域data。這樣內部類Inner中的打印語句:

                 System.out.println(data);

     實際上運行的時候調用的是:

                 System.out.println(this$0.access$0(Outer));

 

總結一下編譯器對類中內部類作的手腳吧:

(1)  在內部類中偷偷摸摸的建立了包可見構造器,從而使外部類得到了建立權限。

(2)  在外部類中偷偷摸摸的建立了訪問私有變量的靜態方法,從而 使 內部類得到了訪問權限。

這樣,類中定義的內部類不管私有,公有,靜態均可以被包圍它的外部類所訪問。

 

二、靜態內部類  ——  在方法間定義的內部類,靜態

    內部類也有靜態的區別,這就是靜態內部類,咱們來看看代碼:

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

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

 
  1. //反編譯代碼 
  2. class Outer$Inner 
  3.     private Outer$Inner(); 
  4.     Outer$Inner(hr.test.Outer$Inner); 

    與上面私有內部類反編譯1比較發現,少了一個指向外圍類對象的引用final Outer this$0; 也就是說靜態內部類沒法獲得其外圍類對象的引用,那麼天然也就沒法訪問外圍類的非靜態成員了。所以,靜態內部類只能訪問其外圍類的靜態成員,除此以外與非靜態內部類沒有任何區別。

 

三、局部內部類 —— 在方法中定義的內部類

      方法內部類也有兩個特色

      (1)  方法中的內部類沒有訪問修飾符, 即方法內部類對包圍它的方法以外的任何東西都不可見。

      (2)  方法內部類只可以訪問該方法中的局部變量,因此也叫局部內部類。並且這些局部變量必定要是final修飾的常量。

例如

 
  1. class Outter{ 
  2.     public void outMethod(){ 
  3.         final int beep=0; 
  4.         class Inner{ 
  5.             //使用beep 
  6.         } 
  7.  
  8.         Inner in=new Inner(); 
  9.     } 

      這又是爲何呢?

      (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) 在方法中定義的匿名內部類:

       ● 沒有構造器,取而代之的是將構造器參數傳遞給超類構造器。

相關文章
相關標籤/搜索