Java內部類與final關鍵字詳解


閱讀目錄

一、內部類的幾種創建方法:

1、成員內部類

1 class Outer{
2 private int i = 1; 
3     class Inner{ 
4         public void fun() {System.out.println("Outer I=" + i)} 
5         } 
6     } 

 

2、方法內部類

1 class Outer{ 
2 public void fun() { 
3     final int i = 1; // 被方法內部類訪問的局部變量必須被final修飾
4     class Inner{ // 方法內部類 不能有訪問修飾符,比如public
5         public void print() {System.out.println("Method I=" + i)} 
6             } 
7         } 
8     } 

3、匿名內部類

 
 1 interface USB { 
 2     public abstract void start() 
 3     } 
 4 class Outer{ 
 5     public void fun() { 
 6     final int i = 1; // 被匿名內部類訪問的局部變量必須被final修飾
 7     new USB(){ 
 8 @Override
 9     public void start(){ 
10                     System.out.println("local_var_i=" + i); 
11                 } 
12             }.start(); 
13         } 
14     } 

4、靜態內部類

1 class Outer{ 
2   private int i = 1; 
3   static class Inner{ // 不能訪問外部類的非靜態成員
4     public void fun() { } 
5       } 
6 }

5、接口內部類

1 interface USB { 
2     class Inner { // 默認是public static,即可以直接new USB.Inner();
3     } 
4 } 

二、神馬是內部類?

乍一看,好些創建方式,挺復雜的吧?首先內部類是個啥東西? 
定義:創建在一個類內部的類型。根據創建位置的不同,分為成員的、方法的、匿名的。接口中的內部類叫做接口內部類。 
理解:在類的內部創建,那就是類的一部分,跟屬性、方法同級。這也就解釋了為何能訪問外部私有成員,我是你的一部分,我當然可以訪問了。

問題的引出<理解內部類>:

那問題來了,我是你的一部分,別人繼承了外部類,會不會也把內部類也繼承過去呢?這得從內部類設計的初衷與面向對象來探討了。 
比如我們用Body類來描述人體。如果是描述人類的話,會去描述人類具有的屬性跟功能。 
那現在我們描述人體,人體內部有心肝脾胃腎,這些再用屬性來描述就不合適了吧?那怎么辦呢?我們可以用內部類去描述。 
所以,無論內部類是公開的還是私有的,都不會被繼承,因為他不是屬性,也不是方法。而是一個內部事務的描述,我們稱之為內部類。 
內部類可以更好地對內部事務進行封裝,看例子: 

身體類中,有屬於身體的各個器官,各個器官有自己的功能與屬性,於是我們把它封裝成一個內部類去單獨描述。 
用來描述器官的內部類是身體的一部分,所以可以去自由的訪問身體的資源(屬性與功能),各個器官與身體相互協調完成運作。 

 1 public class Body { 
 2   private class Heart { // 心臟
 3     public void bloodSupply() { 
 4     // 供血...
 5         } 
 6      } 
 7   private class Hepar { // 肝臟
 8     public void Metabolism() { 
 9     // 代謝...
10         } 
11      } 
12   private class Spleen { // 脾臟
13     public void storageBlood() { 
14     // 儲血...
15            } 
16       } 
17   private class Stomach { //胃部
18     public void digest() { 
19     // 消化...
20            } 
21       } 
22   private class Kidney { // 腎臟
23     public void dischargePoison() { 
24     // 排毒...
25            } 
26       } 
27 }

問題的引出<內部類訪問外部類成員方式>:

因為可以訪問外部私有成員,那問題也就誕生了,他是怎么訪問的呢? 

1:對於成員內部類來說,他會持有一份外部類當前對象的引用,Outer.this。 
這樣就可以調用外部類可見的方法跟成員變量,調用方法是通過持有的外部類引用去調用的。Outer.this.fun(); 
那對於private成員是如何去訪問的呢?是通過編譯器在外部類中生成的靜態的access$0()方法來訪問的。Outer.this.access$0(Outer); 





 
2:對於方法內部類(匿名內部類)來說,因為內部類要訪問所在方法中的局部變量,這時候用持有的外部類當前對象引用還能調用的到嗎? 
調不到了,那Java語言的設計者是這么來解決這個問題的:將局部變量復制一份給內部類使用,怎么復制的?在內部類初始化的時候通過構造方法傳值的方式。 
這樣,內部類中會有一份復制的private修飾的成員變量。這樣我就能訪問了。但問題又來了,比如:

 

1 public void fun() { 
2   int i = 1; 
3   class Inner{ 
4     // int i = 1; 由編譯器生成
5     publicvoid print() { i++ } 
6      } 
7      System.out.println("i=" + i);  
8   // 還是會輸出i=1,我們明明在內部類方法中對此變量進行++了啊。抱歉,您++的是被復制的另一份。
9 }  

問題的引出<保持兩個不同變量的一致性>:


好,問題就這么無情的出現了,怎么解決?Java又說了,要不然給局部變量加上final吧,這樣就會保持值得一致性了。 
ok,問題解決,這就是為什么方法內部類訪問的局部變量必須被final修飾的終極原因(為了約束兩個不同變量的一致性)。設計問題,挺無奈的解決方案。 


來看看另一種說法: 
在Java中,方法的局部變量位於棧上,對象位於堆上。 
因為局部變量的范圍被限制在該方法內,當一個方法結束時,棧結構被刪除,該變量消失。 
但是,定義在這個類中的內部類對象仍然存活在堆上,所以內部類對象不能使用局部變量。除非這些局部變量被標識為最終的。  


這種說法是片面的,因為根本原因是內部類對象無法訪問局部變量,才會去復制一份。 
為了保證兩個變量的一致性,才去使用final關鍵字修飾局部變量。而不是因為棧生命周期與堆生命周期不一致的問題。 

三、最后來說說final關鍵字:

用final關鍵字修飾對象變量,只是不允許這個對象引用再指向其他的對象,但是這個引用所指向的對象的內容是可以改變的 
比如一個經典的例子: 

1 final StringBuffer sb = new StringBuffer("HelloWorld"); 
2 sb = new StringBuffer("Hello"); // 編譯失敗,不能修改sb引用的指向
3 sb.append("China"); // sb指向的對象可以被修改。

其實從final的位置就看出來了,他是在修飾引用類型變量sb,而不是在修飾堆中的實例對象(new StringBuffer("HelloWorld");)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM