Java 中的內部類和匿名類
Java 內部類有什么好處?為什么需要內部類? 首先舉一個簡單的例子,如果你想實現一個接口,但是這個接口中的一個方法和你構想的這個類中的一個方法的名稱,參數相同,你應該怎么辦?這時候,你可以建一個內部類實現這個接口。由于內部類對外部類的所有內容都是可訪問的,所以這樣做可以完成所有你直接實現這個接口的功能。 不過你可能要質疑,更改一下方法的不就行了嗎? 的確,以此作為設計內部類的理由,實在沒有說服力。 真正的原因是這樣的,java 中的內部類和接口加在一起,可以的解決常被 C++ 程序員抱怨 java 中存在的一個問題??沒有多繼承。實際上,C++ 的多繼承設計起來很復雜,而 java 通過內部類加上接口,可以很好的實現多繼承的效果。 內部類:一個內部類的定義是定義在另一個內部的類。 原因是: 1.一個內部類的對象能夠訪問創建它的對象的實現,包括私有數據。 2.對于同一個包中的其他類來說,內部類能夠隱藏起來。 3.匿名內部類可以很方便的定義回調。 4.使用內部類可以非常方便的編寫事件驅動程序。 1.內部類 提起 Java 內部類(Inner Class)可能很多人不太熟悉,實際上類似的概念在 C++ 里也有,那就是嵌套類(Nested Class),關于這兩者的區別與聯系,在下文中會有對比。內部類從表面上看,就是在類中又定義了一個類(下文會看到,內部類可以在很多地方定義),而實際上并沒有那么簡單,乍看上去內部類似乎有些多余,它的用處對于初學者來說可能并不是那么顯著,但是隨著對它的深入了解,你會發現Java的設計者在內部類身上的確是用心良苦。學會使用內部類,是掌握Java高級編程的一部分,它可以讓你更優雅地設計你的程序結構。下面從以下幾個方面來介紹: * 第一次見面public interface Contents {int value();}public interface Destination {String readLabel();}public class Goods {private class Content implements Contents {private int i = 11;public int value() {return i;}}protected class GDestination implements Destination {private String label;private GDestination(String whereTo) {label = whereTo;}public String readLabel() {return label;}}public Destination dest(String s) {return new GDestination(s);}public Contents cont() {return new Content();}}class TestGoods {public static void main(String[] args) {Goods p = new Goods();Contents c = p.cont();Destination d = p.dest("Beijing");}}
在這個例子里類 Content 和 GDestination 被定義在了類 Goods 內部,并且分別有著 protected 和 private 修飾符來控制訪問級別。Content 代表著 Goods 的內容,而 GDestination 代表著 Goods 的目的地。它們分別實現了兩個接口 Content 和 Destination。在后面的 main 方法里,直接用 Contents c 和 Destination d 進行操作,你甚至連這兩個內部類的名字都沒有看見!這樣,內部類的第一個好處就體現出來了??隱藏你不想讓別人知道的操作,也即封裝性。 同時,我們也發現了在外部類作用范圍之外得到內部類對象的第一個方法,那就是利用其外部類的方法創建并返回。上例中的 cont() 和 dest() 方法就是這么做的。那么還有沒有別的方法呢?當然有,其語法格式如下: outerObject=new outerClass(Constructor Parameters); outerClass.innerClass innerObject=outerObject.new InnerClass(Constructor Parameters); 注意在創建非靜態內部類對象時,一定要先創建起相應的外部類對象。至于原因,也就引出了我們下一個話題?? * 非靜態內部類對象有著指向其外部類對象的引用 對剛才的例子稍作修改:public class Goods {private valueRate=2;private class Content implements Contents {private int i = 11 * valueRate;public int value() {return i;}}protected class GDestination implements Destination {private String label;private GDestination(String whereTo) {label = whereTo;}public String readLabel() {return label;}}public Destination dest(String s) {return new GDestination(s);}public Contents cont() {return new Content();}}
修改的部分用藍色顯示了。在這里我們給 Goods 類增加了一個 private 成員變量 valueRate,意義是貨物的價值系數,在內部類 Content 的方法 value() 計算價值時把它乘上。我們發現,value() 可以訪問 valueRate,這也是內部類的第二個好處??一個內部類對象可以訪問創建它的外部類對象的內容,甚至包括私有變量!這是一個非常有用的特性,為我們在設計時提供了更多的思路和捷徑。要想實現這個功能,內部類對象就必須有指向外部類對象的引用。Java 編譯器在創建內部類對象時,隱式的把其外部類對象的引用也傳了進去并一直保存著。這樣就使得內部類對象始終可以訪問其外部類對象,同時這也是為什么在外部類作用范圍之外向要創建內部類對象必須先創建其外部類對象的原因。 有人會問,如果內部類里的一個成員變量與外部類的一個成員變量同名,也即外部類的同名成員變量被屏蔽了,怎么辦?沒事,Java里用如下格式表達外部類的引用: outerClass.this 有了它,我們就不怕這種屏蔽的情況了。 * 靜態內部類 和普通的類一樣,內部類也可以有靜態的。不過和非靜態內部類相比,區別就在于靜態內部類沒有了指向外部的引用。這實際上和 C++ 中的嵌套類很相像了,Java 內部類與 C++ 嵌套類最大的不同就在于是否有指向外部的引用這一點上,當然從設計的角度以及以它一些細節來講還有區別。 除此之外,在任何非靜態內部類中,都不能有靜態數據,靜態方法或者又一個靜態內部類(內部類的嵌套可以不止一層)。不過靜態內部類中卻可以擁有這一切。這也算是兩者的第二個區別吧。 * 局部內部類 是的,Java 內部類也可以是局部的,它可以定義在一個方法甚至一個代碼塊之內。public class Goods1 {public Destination dest(String s) {class GDestination implements Destination {private String label;private GDestination(String whereTo) {label = whereTo;}public String readLabel() { return label; }}return new GDestination(s);}public static void main(String[] args) {Goods1 g= new Goods1();Destination d = g.dest("Beijing");}}
上面就是這樣一個例子。在方法dest中我們定義了一個內部類,最后由這個方法返回這個內部類的對象。如果我們在用一個內部類的時候僅需要創建它的一個對象并創給外部,就可以這樣做。當然,定義在方法中的內部類可以使設計多樣化,用途絕不僅僅在這一點。
下面有一個更怪的例子:public class Goods2{private void internalTracking(boolean b) {if(b) {class TrackingSlip {private String id;TrackingSlip(String s) {id = s;}String getSlip() { return id; }}TrackingSlip ts = new TrackingSlip("slip");String s = ts.getSlip();}}public void track() { internalTracking(true); }public static void main(String[] args) {Goods2 g= new Goods2();g.track();}}
你不能在 if 之外創建這個內部類的對象,因為這已經超出了它的作用域。不過在編譯的時候,內部類 TrackingSlip 和其他類一樣同時被編譯,只不過它由它自己的作用域,超出了這個范圍就無效,除此之外它和其他內部類并沒有區別。 2.匿名類 匿名類是不能有名稱的類,所以沒辦法引用他們。必須在創建時,作為new語句的一部分來聲明他們。 這就要采用另一種形式的new語句,如下所示: new <類或接口> <類的主體> 這種形式的new語句聲明一個新的匿名類,他對一個給定的類進行擴展,或實現一個給定的接口。他還創建那個類的一個新實例,并把他作為語句的結果而返回。要擴展的類和要實現的接口是new語句的操作數,后跟匿名類的主體。 假如匿名類對另一個類進行擴展,他的主體能夠訪問類的成員、覆蓋他的方法等等,這和其他任何標準的類都是相同的。假如匿名類實現了一個接口,他的主體必須實現接口的方法。 注意匿名類的聲明是在編譯時進行的,實例化在運行時進行。這意味著for循環中的一個new語句會創建相同匿名類的幾個實例,而不是創建幾個不同匿名類的一個實例。 從技術上說,匿名類可被視為非靜態的內部類,所以他們具備和方法內部聲明的非靜態內部類相同的權限和限制。 假如要執行的任務需要一個對象,但卻不值得創建全新的對象(原因可能是所需的類過于簡單,或是由于他只在一個方法內部使用),匿名類就顯得很有用。匿名類尤其適合在Swing應用程式中快速創建事件處理程式。interface pr {void print1();}public class noNameClass {public pr dest() {return new pr() {public void print1() {System.out.println("Hello world!!");}};}}public static void main(String args[]) {noNameClass c = new noNameClass();pr hw = c.dest();hw.print1();}
pr 也可以是一個類,但是你外部調用的方法必須在你的這個類或接口中聲明,外部不能調用匿名類內部的方法。
Java 中內部匿名類用的最多的地方也許就是在 Frame 中加入 Listner 了吧。import java.awt.*;import java.awt.event.*;public class QFrame extends Frame {public QFrame() {this.setTitle(\"my application\");addWindowListener(new WindowAdapter() {public void windowClosing(WindowEvent e) {dispose();System.exit(0);}});this.setBounds(10,10,200,200);}}
內部匿名類,就是建立一個內部的類,但沒有給你命名,也就是沒有引用實例的變量。new WindowAdapter() {public void windowClosing(WindowEvent e) {dispose();System.exit(0);}}
new 是建立一個 WindowAdapter 對象,后面一個 {} 表示這個括號中的操作作用于這個默認的對名象,而上面的 Java 程序中后面是一個函數體。 這個用法的作用是:創建一個對象的實例,并且 override 它的一個函數。 打開 WindowAdapter 的代碼可以發現。它是一個抽象類。它是對 WindowListener 接口的一個實現。 Frame.addWindowListner(); 的參數是一個 WindowListner ,而實現上是傳一個從WindowAdapter 派生出的一個匿名類。 有一點需要注意的是,匿名內部類由于沒有名字,所以它沒有構造函數(但是如果這個匿名內部類繼承了一個只含有帶參數構造函數的父類,創建它的時候必須帶上這些參數,并在實現的過程中使用 super 關鍵字調用相應的內容)。如果你想要初始化它的成員變量,有下面幾種方法: 1. 如果是在一個方法的匿名內部類,可以利用這個方法傳進你想要的參數,不過記住,這些參數必須被聲明為 final 。 2. 將匿名內部類改造成有名字的局部內部類,這樣它就可以擁有構造函數了。 3. 在這個匿名內部類中使用初始化代碼塊。
public interface Contents { int value(); } public interface Destination { String readLabel(); } public class Goods { private class Content implements Contents { private int i = 11; public int value() { return i; } } protected class GDestination implements Destination { private String label; private GDestination(String whereTo) { label = whereTo; } public String readLabel() { return label; } } public Destination dest(String s) { return new GDestination(s); } public Contents cont() { return new Content(); } } class TestGoods { public static void main(String[] args) { Goods p = new Goods(); Contents c = p.cont(); Destination d = p.dest("Beijing"); } } |
在這個例子里類 Content 和 GDestination 被定義在了類 Goods 內部,并且分別有著 protected 和 private 修飾符來控制訪問級別。Content 代表著 Goods 的內容,而 GDestination 代表著 Goods 的目的地。它們分別實現了兩個接口 Content 和 Destination。在后面的 main 方法里,直接用 Contents c 和 Destination d 進行操作,你甚至連這兩個內部類的名字都沒有看見!這樣,內部類的第一個好處就體現出來了??隱藏你不想讓別人知道的操作,也即封裝性。
同時,我們也發現了在外部類作用范圍之外得到內部類對象的第一個方法,那就是利用其外部類的方法創建并返回。上例中的 cont() 和 dest() 方法就是這么做的。那么還有沒有別的方法呢?當然有,其語法格式如下:
outerObject=new outerClass(Constructor Parameters);
outerClass.innerClass innerObject=outerObject.new InnerClass(Constructor Parameters);
注意在創建非靜態內部類對象時,一定要先創建起相應的外部類對象。至于原因,也就引出了我們下一個話題??
* 非靜態內部類對象有著指向其外部類對象的引用
對剛才的例子稍作修改:
public class Goods { private valueRate=2; private class Content implements Contents { private int i = 11 * valueRate; public int value() { return i; } } protected class GDestination implements Destination { private String label; private GDestination(String whereTo) { label = whereTo; } public String readLabel() { return label; } } public Destination dest(String s) { return new GDestination(s); } public Contents cont() { return new Content(); } } |
修改的部分用藍色顯示了。在這里我們給 Goods 類增加了一個 private 成員變量 valueRate,意義是貨物的價值系數,在內部類 Content 的方法 value() 計算價值時把它乘上。我們發現,value() 可以訪問 valueRate,這也是內部類的第二個好處??一個內部類對象可以訪問創建它的外部類對象的內容,甚至包括私有變量!這是一個非常有用的特性,為我們在設計時提供了更多的思路和捷徑。要想實現這個功能,內部類對象就必須有指向外部類對象的引用。Java 編譯器在創建內部類對象時,隱式的把其外部類對象的引用也傳了進去并一直保存著。這樣就使得內部類對象始終可以訪問其外部類對象,同時這也是為什么在外部類作用范圍之外向要創建內部類對象必須先創建其外部類對象的原因。
有人會問,如果內部類里的一個成員變量與外部類的一個成員變量同名,也即外部類的同名成員變量被屏蔽了,怎么辦?沒事,Java里用如下格式表達外部類的引用:
outerClass.this
有了它,我們就不怕這種屏蔽的情況了。
* 靜態內部類
和普通的類一樣,內部類也可以有靜態的。不過和非靜態內部類相比,區別就在于靜態內部類沒有了指向外部的引用。這實際上和 C++ 中的嵌套類很相像了,Java 內部類與 C++ 嵌套類最大的不同就在于是否有指向外部的引用這一點上,當然從設計的角度以及以它一些細節來講還有區別。
除此之外,在任何非靜態內部類中,都不能有靜態數據,靜態方法或者又一個靜態內部類(內部類的嵌套可以不止一層)。不過靜態內部類中卻可以擁有這一切。這也算是兩者的第二個區別吧。
* 局部內部類
是的,Java 內部類也可以是局部的,它可以定義在一個方法甚至一個代碼塊之內。
public class Goods1 { public Destination dest(String s) { class GDestination implements Destination { private String label; private GDestination(String whereTo) { label = whereTo; } public String readLabel() { return label; } } return new GDestination(s); } public static void main(String[] args) { Goods1 g= new Goods1(); Destination d = g.dest("Beijing"); } } |
上面就是這樣一個例子。在方法dest中我們定義了一個內部類,最后由這個方法返回這個內部類的對象。如果我們在用一個內部類的時候僅需要創建它的一個對象并創給外部,就可以這樣做。當然,定義在方法中的內部類可以使設計多樣化,用途絕不僅僅在這一點。
下面有一個更怪的例子:
public class Goods2{ private void internalTracking(boolean b) { if(b) { class TrackingSlip { private String id; TrackingSlip(String s) { id = s; } String getSlip() { return id; } } TrackingSlip ts = new TrackingSlip("slip"); String s = ts.getSlip(); } } public void track() { internalTracking(true); } public static void main(String[] args) { Goods2 g= new Goods2(); g.track(); } } |
你不能在 if 之外創建這個內部類的對象,因為這已經超出了它的作用域。不過在編譯的時候,內部類 TrackingSlip 和其他類一樣同時被編譯,只不過它由它自己的作用域,超出了這個范圍就無效,除此之外它和其他內部類并沒有區別。
2.匿名類
匿名類是不能有名稱的類,所以沒辦法引用他們。必須在創建時,作為new語句的一部分來聲明他們。
這就要采用另一種形式的new語句,如下所示:
new <類或接口> <類的主體>
這種形式的new語句聲明一個新的匿名類,他對一個給定的類進行擴展,或實現一個給定的接口。他還創建那個類的一個新實例,并把他作為語句的結果而返回。要擴展的類和要實現的接口是new語句的操作數,后跟匿名類的主體。
假如匿名類對另一個類進行擴展,他的主體能夠訪問類的成員、覆蓋他的方法等等,這和其他任何標準的類都是相同的。假如匿名類實現了一個接口,他的主體必須實現接口的方法。
注意匿名類的聲明是在編譯時進行的,實例化在運行時進行。這意味著for循環中的一個new語句會創建相同匿名類的幾個實例,而不是創建幾個不同匿名類的一個實例。
從技術上說,匿名類可被視為非靜態的內部類,所以他們具備和方法內部聲明的非靜態內部類相同的權限和限制。
假如要執行的任務需要一個對象,但卻不值得創建全新的對象(原因可能是所需的類過于簡單,或是由于他只在一個方法內部使用),匿名類就顯得很有用。匿名類尤其適合在Swing應用程式中快速創建事件處理程式。
interface pr { void print1(); } public class noNameClass { public pr dest() { return new pr() { public void print1() { System.out.println("Hello world!!"); } }; } } public static void main(String args[]) { noNameClass c = new noNameClass(); pr hw = c.dest(); hw.print1(); } |
pr 也可以是一個類,但是你外部調用的方法必須在你的這個類或接口中聲明,外部不能調用匿名類內部的方法。
Java 中內部匿名類用的最多的地方也許就是在 Frame 中加入 Listner 了吧。
import java.awt.*; import java.awt.event.*; public class QFrame extends Frame { public QFrame() { this.setTitle(\"my application\"); addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { dispose(); System.exit(0); } }); this.setBounds(10,10,200,200); } } |
內部匿名類,就是建立一個內部的類,但沒有給你命名,也就是沒有引用實例的變量。
new WindowAdapter() { public void windowClosing(WindowEvent e) { dispose(); System.exit(0); } } |
new 是建立一個 WindowAdapter 對象,后面一個 {} 表示這個括號中的操作作用于這個默認的對名象,而上面的 Java 程序中后面是一個函數體。
這個用法的作用是:創建一個對象的實例,并且 override 它的一個函數。
打開 WindowAdapter 的代碼可以發現。它是一個抽象類。它是對 WindowListener 接口的一個實現。
Frame.addWindowListner(); 的參數是一個 WindowListner ,而實現上是傳一個從WindowAdapter 派生出的一個匿名類。
有一點需要注意的是,匿名內部類由于沒有名字,所以它沒有構造函數(但是如果這個匿名內部類繼承了一個只含有帶參數構造函數的父類,創建它的時候必須帶上這些參數,并在實現的過程中使用 super 關鍵字調用相應的內容)。如果你想要初始化它的成員變量,有下面幾種方法:
1. 如果是在一個方法的匿名內部類,可以利用這個方法傳進你想要的參數,不過記住,這些參數必須被聲明為 final 。
2. 將匿名內部類改造成有名字的局部內部類,這樣它就可以擁有構造函數了。
3. 在這個匿名內部類中使用初始化代碼塊。