一、匿名內部類
像下邊這樣雖然返回的是B的對象,但是其後卻帶有B的{},括號中可以是類的成員和方法。這種類的形式由於對外是隱藏的,且沒有直接的類名,所以稱爲匿名內部類。
public class A{
public B getB(){
return new B(){};
}
}
這裏需要注意的一點是,如果在匿名內部類中使用外部定義的對象,那麼編譯器要求其參數引用是final的(如果不使用就不需要)。
public class A{
public B getB(final String name){
return new B(){
private String st=name;//name必須是final
public String getStr(){
return st;
}
};
}
}
因爲匿名內部類是沒有名字的,所以它是不可能有命名構造器的,但是可以通過實例初始化達到爲匿名內部類創建一個構造器的效果。
interface A{
public void f();
}
public class UseA{
public static A getA(final int i){
//因內部類要使用i,所以應爲final
return new A(){
public void f(){
System.out.print("this num="+i);
}
};//通過實例初始化創建構造器效果
}
public static void main(String[] args){
A a=getA(10);
a.f();
}
}
out:
this num=10
但是要注意的是這種方式創建的構造器有一個限制——實力初始化方法不能被重載,所以只能有一個這樣的構造器。
注:匿名內部類與正規的繼承相比有些受限,因爲匿名內部類既可以擴展類,也可以實現接口,但是不能倆者兼備。而且如果實現接口,也只能實現一個接口。
看一下反編譯後內部類的效果:
final class UseA$1 implements A
{
UseA$1(int paramInt) {}
public void f()
{
System.out.print("this num=" + this.val$i);
}
}
二、嵌套類
帶有static的內部類被稱爲嵌套類(要知道普通的內部類對象隱式地保存了一個引用,指向創建它的外圍對象)。而此時的內部類會有倆個特點:
- 創建嵌套類對象不需要外圍類的對象。
- 不能從嵌套類的對象中訪問非靜態的外圍類對象。
注意:嵌套類沒有this引用。
嵌套類可以作爲接口(接口中除了一些聲明外本不能存放任何代碼)的一部分,放到接口中的任何類自動地是public和static,且可以在內部類中實現外部接口。
public interface A{
void f();
class B implements A{
public void f(){
System.out.print("Hello!");
}
public static void main(String[] args){
new B().f();//可以直接通過new進行對象創建(static讓其變成了獨立的公共類)
}
}
}
上邊的例子經過編譯不會出現任何問題,但是在執行時會報錯:
原因:
如果在執行時直接使用"java A",那當然會報錯,因爲嵌套類本就不屬於接口A(所以此時接口A中實際上並沒有其他代碼),看一下編譯後嵌套類的效果:
public class A$B implements A
{
public void f()
{
System.out.print("Hello!");
}
public static void main(String[] paramArrayOfString)
{
new B().f();
}
}
很顯然main方法在A$B類(B類此時在接口A的命名空間中)中,所以要使用”java A$B“來執行代碼,此時A$B實際是一個獨立的類(Linux中需要將$進行轉義)。三、爲什麼要使用內部類?
一般來說內部類繼承自某個類或者實現某個接口,內部類的代碼操作創建它的外圍類的對象。所以可以認爲內部類提供了某種進入其外圍類的窗口。
*最吸引人的原因:
每個內部類都能獨立地繼承自一個(接口的)實現,所以無論外圍類是否已經繼承了某個(接口的)實現,對於內部類都沒有影響。
解釋:
也就是對於一個普通類來說在不考慮內部類來說,要實現多繼承機制只能通過”繼承一個類+實現多個接口“或”只實現多個接口“。但是如果沒有接口而只有類或抽象類呢?那要想實現多繼承就只能通過內部類實現,見如下實例:
class B{
void show(){
System.out.println("This is B!");
}
}
abstract class C{
public int i=10;
abstract void abShow();
public void showI(){}
}
public class A extends B{ //主類繼承B
class Inner extends C{ //內部類繼承C
public void abShow(){
System.out.println("This is C!");
}
C makeC(){
return new C(){ //匿名內部類繼承C並重寫C中方法
public void abShow(){
System.out.println("This is makeC!");
}
public void showI(){
System.out.println("i="+i);
}
};
}
}
public static void main(String[] args){
A a=new A();
A.Inner in=a.new Inner();
C c=in.makeC();
a.show();
in.abShow();
c.abShow();
c.showI();
}
}
out:
This is B!
This is C!
This is makeC!
i=10
*內部類的一些特性:
- 內部類可以有多個實例,每個實例都有自己的狀態信息,並且與其外圍類對象的信息相互獨立。
- 在單個外圍類中,可以讓多個內部類以不同的方式實現同一個接口,或繼承同一個類(就如上邊的例子)。
- 創建內部類對象的時刻並不依賴於外圍類對象的創建。
- 內部類並沒有令人迷惑的”is-a“關係;它就是一個獨立的實體。
*閉包與回調(callback):
- 閉包——是一個可調用的對象,它記錄了一些信息,這些信息來自於創建它的作用域。
- 得出結論——內部類就是一個面向對象的閉包(不僅包含外圍類對象的信息還自動有一個指向此外圍類對象的引用)。
- 回調——通過回調,對象可以攜帶一些信息,這些信息允許它在稍後的某個時刻調用初始化的對象。它的價值在於它的靈活性,可以在運行時動態地決定需要調用什麼方法。
*內部類在控制框架中的使用:
控制框架由一個或一組類構成,用以將變化的事物與不變的事物相互分離。
- 控制框架的完整實現是由單個的類創建的,從而使得實現的細節被封裝。內部類用來表示解決問題所必需的各種不同的action()。
- 內部類可以很容易地訪問外圍類的任意成員,所以可以避免這種實現變得笨拙。如果沒有這種能力,代碼將變得令人討厭,以至於你肯定會選擇別的方法。
使用內部類可以在單一的類中產生對同一個基類Event的多種導出版本。
*內部類標識符:
前邊的反編譯.class文件中顯示了每個對象的全部信息,內部類在編譯時同樣會生成一個獨立的.class文件。可以看出這些文件的命名有嚴格的規則:外圍類的名字,加上”$“,再加上內部類的名字就是一個內部類的.class文件名:
class A$Inner$1 extends C
{
A$Inner$1(A.Inner paramInner) {}
public void abShow()
{
System.out.println("This is makeC!");
}
public void showI()
{
System.out.println("i=" + this.i);
}
}
在名字中可以看到一個數字”1“,這個代表匿名內部類,由於匿名內部類沒有名字,所以編譯器會簡單的產生一個數字作爲其標識符。如果內部類是嵌套在別的內部類中,需要在最內層的類名前再加一個”$“。----------------------------------------------------------------------------------------------
接口和內部類的產生使得Java的多繼承機制更加完善,尤其對於內部類來說,其允許繼承多個非接口類型(類或抽象類),並且比C++中多繼承更好理解和使用。但是在實際開發中什麼時候應該用這些技術,應該在設計階段考慮。