java內部類是指一個類定義在另一個類的內部,其中的方法可以訪問包含他們外部類的域。這是一種比較復雜的技術,內部類的主要用于設計那些有協作性關系的類之間。特別是在java處理GUI事件中得到了廣泛的應用。除此之外,內部類最常用的原因有以下幾點:
內部類方法可以訪問該類定義所在的作用域中的數據,包括私有的數據 內部類可以對同一個包中的其他類隱藏起來 用匿名內部類來定義回調函數會方便很多 內部類能獨立地繼承一個(接口的)實現,無論外圍類是否已經繼承了某個(接口的)實現,對于內部類沒有影響。前兩點很好理解,第三點涉及到一個回調函數的概念,回調函數有反饋的作用,這里推薦一篇很好理解的文章:
回調函數傳送門:http://blog.csdn.net/xiaanming/article/details/8703708/
內部類是一個非常復雜的功能。千里之行,始于足下,在徹底研究透徹之前,我們不妨從一個最簡單的例子開始看起。
package Inner;/** * * @author QuinnNorris * 外圍類ExampleInner類,其中包含一個內部類InnerClass */public class ExampleInner { PRivate int interval; private boolean beep;//一個在ExampleInner內的變量,在內部類InnerClass中,我們會用到它 public ExampleInner(int interval, boolean beep) { this.interval=interval; this.beep=beep; }//構造器 public void start() { InnerClass ic = new InnerClass(); }//start方法創建內部類對象 //內部類 public class InnerClass { public void action() { if (beep) System.out.println("beep is true!"); } }}需要注意,這里的InnerClass位于ExampleInner內部,但這并不意味著每個ExampleInner對象都有一個InnerClass實例域。
在這里,我們把包含內部類的類暫且先叫做外圍類。
在上面的代碼中,內部類InnerClass中的方法中有一個beep變量,但令人驚訝的是,在InnerClass中并沒有這個beep變量,之所以這段代碼沒有問題,是因為內部類自動的引用了外圍類中的這個beep變量。內部類是怎樣做到這一點的呢?
事實上,內部類的對象會自動的維護一個隱式引用,這個引用指向了創建它的外圍類對象。這個引用的語法表達式為:
OuterClass.this //內部類的隱式引用,指向外圍類,在上述例子中為:ExampleInner.this也就是說,我們內部類中的action方法可以這樣來編寫:
public void action() { if (ExampleInner.this.beep)//完全寫法,隱式引用OuterClass.this可以省略 System.out.println("beep is true!");}通過這個隱式指針,內部類獲得了比普通類更大的訪問權限——它可以訪問外圍類中的私有域。
這個隱式引用是在構造器中設置的,編譯器修改了所有內部類的構造器,添加了一個外圍類的引用參數。比如,我們上面的InnerClass類沒有構造器,編譯器為其生成了一個默認構造器:
public InnerClass(ExampleInner ei){ ExampleClass.this = ei; //將外圍類的一個對象賦給隱式引用}當我們要實例化一個內部類時我們默認的省略了外圍類的引用,這個引用通常是this引用。
public void start() { InnerClass ic = this.new InnerClass();//this引用指示了創建的內部類的外圍類,通常省略}//start方法創建內部類對象通常情況下,this引用是多余的。正如我們上面說過,InnerClass位于ExampleInner內部,但這并不意味著每個ExampleClass對象都有一個InnerClass實例域。在內部類是公有的情況下,我們可以通過顯示的命名將外圍類引用設置為某個ExampleInner對象,這樣一來我們可以在外圍類的外部隨意的實例化這兩個類對象來使用。 在外圍類作用域之外,引用內部類語法:
OuterClass.InnerClass object = object.new InnerClass();我們在外圍類之外用OuterClass.InnerClass的方法來表示內部類的類名。與此同時,我們需要用一個已經實例化好的外圍類對象object來進行 .new InnerClass( )的操作,此時就不能用this來表示外圍類了,而且這個外圍類對象也不能省略,否則報錯。 下面我們來看一下在測試類中我們創建這兩個類實例的情況。
package Inner;/** * * @author QuinnNorris * 在ExampleInner類的外部,我們通過如下方式將外圍類和內部類實例化。 */public class Test { public static void main(String[] args) { ExampleInner ei = new ExampleInner(10,true);//實例化外圍類 //OuterClass.InnerClass object = object.new InnerClass ExampleInner.InnerClass eiic = ei.new InnerClass(); //ok 通過上述的特殊語法,我們在外圍類的外部,成功的實例化了內部類 ExampleInner.InnerClass eiic = new InnerClass(); //error 此時如果不用外圍類對象去.new InnerClass()會報錯,即使直接引內部類的包也不行 eiic.action();//ok 調用內部類的方法,沒問題 } }當在java1.1的java語言中增加內部類時,很多程序員都認為這將是一項很主要的新特性,但內部類的語法如此麻煩,以至于違背了java要比c++更加簡單的設計理念。盡管如此,雖然內部類很復雜,但它其實只是一種語法糖,虛擬機對內部類一無所知,那么虛擬機中是如何處理內部類的呢?
糖衣語法(語法糖)傳送門:http://blog.csdn.net/quinnnorris/article/details/54849155
為了能夠得到虛擬機中內部類實際的情況,我們使用了javap將內部類的源代碼進行反編譯,結果如下:
public ExampleInner$InnerClass{ public ExampleInner$InnerClass(ExampleInner);//構造器 public void action(); final ExampleInner this$0;}可見,虛擬機中將內部類解釋為:外圍類名+$+內部類名。(這里有個基本知識,java是可以使用$來作為變量名的一部分的,甚至是變量名的開始,但是并不推薦)前兩個函數還是很好理解的,第一個構造器正好印證了我們上面說的自動生成的內容,第二個是內部類中的函數。第三個this$0是一種合成方法,編譯器為了引用外圍類,生成了一個附加的實例域this$0,(這個名字是編譯器合成的,在自己的編寫代碼中不能引用它,而且這個名字是隨機的)。
不僅僅將內部類反編譯,我們將外圍類也反編譯之后會發現,除了其他一樣的代碼外,外圍類會多出一個靜態方法:
static boolean access$0(ExampleInner);這個靜態方法的功能是,將傳入的外圍類對象中的beep變量值返回給內部類。我們上面知道,內部類中需要用到的私有的beep變量,這個方法正是編譯器生成的一個合成方法。如果內部類中需要用到其他的私有域變量,編譯器會繼續的生成這樣的合成方法來返回那些私有域的值。這也正是,為什么內部類可以訪問外圍類的私有域的原因。
如果內部類只在一個方法中被使用,其他的地方無需這個內部類,我們可以把它定義在這個方法中,我們把這種定義在一個方法中(或定義在一個作用域中)的內部類叫做局部內部類。
局部內部類不能用public或private來聲明,它的作用域被限定在聲明這個局部類的塊中,也正因此,局部類對于除了這個方法之外的外部世界可以完全的隱藏起來,除了這個方法,沒人知道有這樣一個類的存在。
package Inner;/** * * @author QuinnNorris * 局部內部類,只對飽含著他的方法或作用域可見 */public class LocalInner { private int interval; private boolean beep; public LocalInner(int interval, boolean beep) { this.interval=interval; this.beep=beep; }//構造器 public void start() { //局部內部類,只對start方法可見 class InnerClass { public void action() { if (beep) System.out.println("beep is true!"); } } InnerClass ic = new InnerClass(); }}相比較其他類型的內部類,局部內部類還有一個顯著的優點。它不僅可以訪問包含著它的外圍類,還可以訪問在方法、作用域中的局部變量,但是前提是這些變量要用final修飾。這個特點看似天經地義,畢竟內部類在方法中,為什么不能訪問方法的局部變量呢?不妨看一下下面的例子:
在有些情況下,在方法執行過程中,方法中的一條語句將局部內部類作為一個參數傳遞給其他的方法,而后方法結束。在其他的方法調用內部類時,內部類中原方法的局部變量已經隨著方法的結束被釋放,這個時候無法找到局部變量。
正是這個原因,才讓我們明白一個局部內部類訪問作用域中的局部變量是需要處理的,不是天經地義的。 編譯器會為那些在局部內部類中要用到的局部變量做備份,也正是做備份的原因,導致局部變量需要用final修飾,如果不是這樣,很有可能局部變量后來被修改,導致和備份的內容不一樣從而出錯。
在局部內部類的基礎上再深入一步,如果我們只需要用到這個類一次,那么我們只需要創建一個對象就好,不用命名了。這種不命名的內部類被稱作匿名內部類。
那么問題來了,如果我們不去命名一個內部類,我們怎么才能知道我們創建的是什么東西呢?解決的方法是,我們用實現一個接口或者擴展一個超類來表示我們正在編寫的內部類。
package Inner;/** * * @author QuinnNorris * 匿名內部類,實現了CallBack接口 */public class AnonymousInner { private int interval; private boolean beep; public AnonymousInner(int interval, boolean beep) { this.interval=interval; this.beep=beep; }//構造器 public void start() { //匿名內部類,我們只知道它是一個實現了CallBack的子類,因為匿名,他沒有自己的名字。 //CallBack接口要存在,我們不可空穴來風的創建匿名類。 CallBack cb = new CallBack(){ public void solve(String result){ result="result"; } }; }}需要注意的是,匿名內部類沒有構造器。這是理所當然的,因為它本身連名字都沒有,它只能使用父類的構造方法,如果匿名內部類實現的是接口那么更簡單,只需要在后面跟上一對空的圓括號即可,就像我們剛才例子中做的那樣。如果是繼承父類,則需要使用父類構造器。
因為匿名內部類也存在于方法之中,所以,局部內部類的final理論也適用于匿名內部類。
匿名類在處理一些代碼較短、事件較為簡單的內容時具有很大的優勢,更切實際,也更易于理解。現在很多很火的技術中,這種匿名機制也已經屢見不鮮了。
有的時候,其實我們用內部類只是為了把一個類隱藏在另外一個類內部,甚至不需要這兩個類之間的聯系。那么這個時候可以把內部類聲明為static,取消兩個類產生的引用,這就是靜態內部類(也叫嵌套內部類)。當我們把什么東西和靜態的聯系到一起時,還是那套理論,這個東西和對象無關,純粹變成了類的產物。那么靜態內部類的特點也很好推斷:
靜態內部類不再有對外圍類的引用特權 靜態內部類能夠使用外圍類的靜態變量,能使用同時也能被外圍類的靜態方法使用 只有內部類可以被聲明為靜態類 如果需要在外圍類之外使用靜態內部類,可以用OuterClass.InnerClass的方法 靜態內部類一般設置為public而不是private,便于調用如果你喜歡,內部類一共可以被分為四種:成員內部類(我們一開始舉例的最普通的內部類),局部內部類,匿名內部類,靜態內部類。這四種各有各的特點,在運用的時候要綜合考慮。除此之外,我們還一起研究了在虛擬機中內部類的實現情況,這是很有必要的,懂得它的原理才能在使用時判斷的更準,在出錯時直擊要害。
新聞熱點
疑難解答