簡單的說就是在一個類、接口或者方法的內部創建另一個類。這樣理解的話創建內部類的方法就很明確了。
package com.gissky.innerClass;public class Path { public class Start{ public Start(String start){ this.start=start; } PRivate String start; public String getStart() { return start; } } public class End{ public End(String end){ this.end=end; } private String end; public String getEnd() { return end; } }}2.內部類的實例化2.1 在外部類的非靜態方法內部實例化,這個很簡單,和我們平時做的一樣。
public Start start(String start){ return new Start(start);}public End end(String end){ return new End(end);}2.2 在外部類的非靜態方法外部的任意位置創建內部類對象的時候,必須具體指明這個對象的類型,即:OuterClassName.InnerClassName,并且必須在有外部類對象的前提下進行,使用類似:外圍類對象.new 內部類();這樣的語法。
public static void main(String[] args){ Path path=new Path(); Path.Start start=path.new Start("my home"); Path.End end=path.new End("fu zhou"); path.Go(start, end);}3. 內部類有一個指向外部類的對象的引用
內部類擁有其外部類的所有元素的訪問權。這是如何做到的呢?修改一下上面的程序:
public class Path { private double startTime; private double endTime; public class Start{ public Start(String start){ startTime=new Date().getTime(); this.start=start; } private String start; public String getStart() { return start; } } public class End{ public End(String end){ this.end=end; endTime =new Date().getTime(); } private String end; public String getEnd() { return end; } } public void Go(Start start,End end){ System.out.println("start to end"); System.out.println(endTime-startTime); } public Start start(String start){ return new Start(start); } public End end(String end){ return new End(end); }}
這是編譯器已經幫我們生成了3個.class文件:
前兩個便是編譯器生成的內部類的.class文件,奇怪的是它們是以其外部類開頭再加上$和自己類的名字。我們用javap反編譯工具查看一下:
由此我們便可以知道,內部類中訪問外部類的成員變量是通過一個調用外部類的一個靜態方法進行的,該靜態方法須傳入一個外圍類的引用。
到這里應該明白2.2中提到的“在外部類的非靜態方法外部的任意位置創建內部類對象的時候,必須具體指明這個對象的類型,即:OuterClassName.InnerClassName,并且必須在有外部類對象的前提下進行”的原因了吧。那是因為,內部類要連接到創建它的外部類對象上。
這樣做不是存在安全風險嗎?這種擔心是很有道理的。任何人都可以通過調用access$0方法很容易地讀取到私有域beep。當然,access$0不是Java的合法方法名。但熟悉類文件結構的黑客可以使用十六進制編輯器輕松地創建一個用虛擬機指令調用那個方法的類文件。由于隱秘地訪問方法需要擁有包可見性,所以攻擊代碼需要與被攻擊類放在同一個包中。
總而言之,如果內部類訪問了私有數據域,就有可能通過附加在外圍類所在包中的其他類訪問它們,但做這些事情需要高超的技巧和極大的決心。程序員不可能無意之中就獲得對類的訪問權限,而必須刻意地構建或修改類文件才有可能達到這個目的。
4. 靜態內部類首先靜態內部類沒有像普通內部類那么多的限制了,它就不需要對外部類對象的引用。
有時候,使用內部類只是為了把一個類隱藏在另外一個類的內部,并不需要內部類引用外圍類對象。為此,可以將內部類聲明為static,以便取消產生的引用。
注釋:在內部類不需要訪問外圍類對象的時候,應該使用靜態內部類。有些程序員用嵌套類(nested class)表示靜態內部類。
注釋:聲明在接口中的內部類自動成為static和public。
5. 內部類與向上轉型public interface Fly { public void fly();}
public class Animal { private class Bird implements Fly{ @Override public void fly() { System.out.println("Bird.flu()"); } } public Fly Bfly(){ return new Bird(); } public static void main(String[] args) { Animal animal=new Animal(); Fly fly=animal.Bfly(); fly.fly(); }}
由上述代碼可知,我們無法將Fly接口向下轉型,因為我們無法知道內部類Bird的名字(它是private)。因此private內部類可以完全阻止任何依賴于類型的編碼。客戶端不能訪問到任何新加在內部類中的接口,所以擴展接口是沒有價值的(阻止了is-like-a形式的出現)。并且由于內部類是private的,因此它對于客戶端來說是完全不可見的,并且不可用。所得到的知識指向基類或接口的引用,方便的隱藏了實現細節。
6. 局部內部類即,定義在方法和作用域內的內部類。使用理由:在你的方法或作用域內由于某種原因要使用一個類來輔助你的程序,但是你又不想這個類是公用的。
如,這樣一個需求:我進行一個評價工作,我選取了指標,然后根據不同的指標讓用戶選擇不同的模型來計算該指標的評價值。這樣一來,我就需要記錄:指標名稱、模型名稱、模型參數。然后通過反射來計算。實際工作中我這樣設計:
public InfModel Execute(InfModel infModel, String measure, Users user) { //region 內部類--用于構造各個指標所使用的模型及參數 class Model{ public Method execute; public Object intance; public String modelParam; /**構造器 * @param execute 模型執行的方法接口 * @param intance 模型實例 * @param modelParam 構造模型所需參數 */ public Model(Method execute,Object intance, String modelParam){ this.modelParam=modelParam; this.execute=execute; this.intance=intance; } } //endregion //后續工作}
局部類不能用public或private訪問說明符進行聲明。它的作用域被限定在聲明這個局部類的塊中。
局部類有一個優勢,即對外部世界可以完全地隱藏起來。除Execute方法之外,沒有任何方法知道Model類的存在。
7.匿名內部類public class AnonymousInnerClass { public Destination destination(final String dest){ return new Destination() { @Override public String readLabel() { return dest; } }; } public static void main(String[] args){ AnonymousInnerClass aic=new AnonymousInnerClass(); System.out.println(aic.destination("天涯海角").readLabel()); }}
由上面的代碼可以看出,匿名內部類也就是沒有類名的一個內部類。因此不要向匿名內部類中添加其基類中沒有的接口(因為加上也是白加,外面是訪問不到的)。但是為了實現的需要可以添加一些私有的成員。同時我們也注意到,內部類內部希望使用一個在其外部定的對象時,編譯器則要求其參數應用必須是final的。
為什么這個參數必須為final的呢?為此我們仔細地考查一下控制流程。
這時在內部類中產生了“閉包”,閉包將使得dest脫離了它所在的方法繼續存在,這樣在readLabel方法中就可以任意的修改dest了,但是外部類對此卻全然不知。因此,dest必須是final的。
其實為了能夠讓readLabel方法工作,Destination類(這里是一個匿名內部類,編譯器給它取的名字為AnonymousInnerClass$1.class)在dest域釋放之前將dest域在自己的構造器中進行備份。
上面的例子對于接口或者一個還有無參構造器的類來說是可行的。但是當一個類的構造器有參數的時候該怎么辦呢?很簡單,就像我們new一個有參構造器的類一樣來構造即可:
public abstract class Base { { System.out.println("Base instane initializer"); } public Base(int i){ System.out.println("Base constructor, i="+i); } public abstract void f();}
public class AnonymousInnerClass { public static Base getBase(int i){ return new Base(i) { { System.out.println("Inside instane initializer"); } @Override public void f() { System.out.println("In AnonymousInnerClass f()"); } }; } public static void main(String[] args){ Base base=getBase(5); base.f(); }}
我們發現這里的變量i沒有要求必須是final的,這是為什么呢?因為,這是變量i不是在內部類內部使用,而是被傳遞給了其基類的構造函數,它并不會在匿名內部類中被直接使用。(?'ω')?注:要不要加final就是看局部內部類的外圍方法結束后,其參數是不是還要被局部內部類使用,如果還要被使用,則要加final否則就不用加了。
新聞熱點
疑難解答