1. 程式人生 > >解惑內部類(結合程式碼,一解就懂)

解惑內部類(結合程式碼,一解就懂)

內部類有兩種情況: (1) 在類中定義一個類(私有內部類,靜態內部類) (2) 在方法中定義一個類(區域性內部類,匿名內部類) 1、私有內部類 —— 在方法之間定義的內部類,非靜態       我們首先看看類中內部類的兩個特點:       (1) 在外部類的作用範圍內可以任意建立內部類物件,即使內部類是私有的(私有內部類)。即內部類對包圍它的外部類可見。 例如 (實際上,內部類是可以設定為public公開的,但一般我是當做成員變數設定為private)
  1. //程式碼1:內部類對外部類可見
  2. class Outer{ 
  3.     //建立私有內部類物件
  4.     public Inner in=new Inner(); 
  5.     //私有內部類
  6.     privateclass Inner{ 
  7.          ... 
  8.     } 
      (2) 在內部類中可以訪問其外部類的所有域,即使是私有域。即外部類對內部類可見。 例如
  1. //程式碼2:外部類對內部類可見
  2. //(內部類可以訪問外部類的所有成員變數和方法)
  3. class Outer{ 
  4.     //外部類私有資料域
  5.     privateint data=0
  6.     //內部類
  7.     class Inner{ 
  8.         void print(){ 
  9.             //內部類訪問外部私有資料域
  10.             System.out.println(data); 
  11.         } 
  12.     } 
      問題來了:上面兩個特點到底如何辦到的呢?內部類的"內部"到底發生了什麼?        其實,內部類是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.     staticint access$0(Outer);  //靜態方法,返回值是外部類私有域 data 的值
    現在可以解釋第二個特點了:為什麼內部類可以引用外部類的私有域?     原因的關鍵就在編譯器在外圍類中添加了靜態方法access$0。 它將返回值作為引數傳遞給他的物件域data。這樣內部類Inner中的列印語句:                  System.out.println(data);      實際上執行的時候呼叫的是:                  System.out.println(this$0.access$0(Outer)); 總結一下編譯器對類中內部類做的手腳吧: (1)  在內部類中偷偷摸摸的建立了包可見構造器,從而使外部類獲得了建立許可權。 (2)  在外部類中偷偷摸摸的建立了訪問私有變數的靜態方法,從而 使 內部類獲得了訪問許可權。 這樣,類中定義的內部類無論私有,公有,靜態都可以被包圍它的外部類所訪問。 2、靜態內部類  ——  在方法間定義的內部類,靜態     內部類也有靜態的區別,這就是靜態內部類,我們來看看程式碼:
  1. package hr.test; 
  2. //程式碼3:靜態內部類對外部變數的引用
  3. publicclass Outer{ 
  4.     privatestaticint i=0
  5.     //建立靜態內部類物件
  6.     public Inner in=new Inner(); 
  7.     //靜態內部類
  8.     privatestaticclass Inner{ 
  9.         publicvoid print(){ 
  10.             System.out.println(i);   //如果i不是靜態變數,這裡將無法通過編譯
  11.         } 
  12.     }  
    靜態內部類和私有內部類最大的區別在於,靜態內部類中無法引用到其外圍類的非靜態成員。這是為什麼?我們還是來看看靜態內部類Outer$Inner中發生了什麼吧?
  1. //反編譯程式碼
  2. class Outer$Inner 
  3.     private Outer$Inner(); 
  4.     Outer$Inner(hr.test.Outer$Inner); 
    與上面私有內部類反編譯1比較發現,少了一個指向外圍類物件的引用final Outer this$0; 也就是說靜態內部類無法得到其外圍類物件的引用,那麼自然也就無法訪問外圍類的非靜態成員了。因此,靜態內部類只能訪問其外圍類的靜態成員,除此之外與非靜態內部類沒有任何區別。 3、區域性內部類 —— 在方法中定義的內部類       方法內部類也有兩個特點       (1)  方法中的內部類沒有訪問修飾符, 即方法內部類對包圍它的方法之外的任何東西都不可見。       (2)  方法內部類只能夠訪問該方法中的區域性變數,所以也叫區域性內部類。而且這些區域性變數一定要是final修飾的常量。 例如
  1. class Outter{ 
  2.     publicvoid outMethod(){ 
  3.         finalint beep=0
  4.         class Inner{ 
  5.             //使用beep
  6.         } 
  7.         Inner in=new Inner(); 
  8.     } 
      這又是為什麼呢?       (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) 在方法中定義的匿名內部類:        ● 沒有構造器,取而代之的是將構造器引數傳遞給超類構造器。