1. 程式人生 > >JAVA中內部類的繼承和覆蓋問題

JAVA中內部類的繼承和覆蓋問題

在JAVA中,由於內部類的構建器必須連線到指向其外圍類物件的引用,所以在繼承內部類的時候會變得很複雜。問題在於指向外圍類物件的祕密引用必須得到初始化,而在匯出類中不再存在可連線的預設物件,我們必須使用特殊的語法來明確說明它們的關係:

enclosingClassReference.super()。

看如下一段程式碼:

package access;
class WithInner{
	class Inner{}
}
public class InheritInner extends WithInner.Inner{
	InheritInner(WithInner x){
		x.super();
	}
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		WithInner x = new WithInner();
		InheritInner i = new InheritInner(x);
	}

}
可以看到InheritInner只繼承了內部類,並沒有繼承外圍類,但若要生成一個構建器時,預設的構建器並不能完成此工作,不能只是傳遞一個指向外圍類物件的引用,必須提供上述語法來確定一個關係。

如果我們建立了一個內部類,然後繼承其外圍類並重新定義內部類的時候會發生什麼?看上去這和覆蓋某種方法類似,但實際則不然,覆蓋內部類就好像是外圍類的一個方法,實際上並不起什麼作用,看如下一段程式碼:

package access;
class Egg{
	private Yolk y;
	protected class Yolk{
		public Yolk(){
			System.out.println("Egg.Yolk()");
		}
	}
	public Egg(){
		System.out.println("New Egg()");
		y = new Yolk();
	}
}
public class BigEgg extends Egg{
	public class Yolk{
		public Yolk(){
			System.out.println("BigEgg.Yolk()");
		}
	}
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		new BigEgg();
	}

}
此程式的執行結果為:


我們可能會與覆蓋方法所混淆,認為既然建立了BigEgg的物件,那麼所使用的應該是覆蓋後的Yolk方法,但從輸出來看並不是這樣,當建立BigEgg物件時,構建器只是會先呼叫Egg的構建方法來初始化一個Egg,所以所有的輸出均來自於Egg,而我們在BigEgg中並沒有呼叫任何的方法來操縱BigEgg中的Yolk方法,只有當出現呼叫時,才會發生輸出,改動一下此程式碼:

package access;
class Egg{
	private Yolk y;
	protected class Yolk{
		public Yolk(){
			System.out.println("Egg.Yolk()");
		}
	}
	public Egg(){
		System.out.println("New Egg()");
		y = new Yolk();
	}
}
public class BigEgg extends Egg{
	private Yolk x;
	public class Yolk{
		public Yolk(){
			System.out.println("BigEgg.Yolk()");
		}
	}
	public BigEgg(){
		System.out.println("New BigEgg()");
		x = new Yolk();
	}
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		new BigEgg();
	}

}
此程式的輸出結果為:


可以看到只有在實際發生呼叫時候才會進行輸出,這與方法覆蓋有著本質的區別。
當我們繼承了某個外圍類的時候,內部類並沒有發生什麼特別神奇的變化,內部類是完全獨立的實體,各自在自己的名稱空間內,看如下一段程式碼:

package access;
class Egg2{
	protected class Yolk{
		public Yolk(){
			System.out.println("Egg2.Yolk()");
		}
		public void f(){
			System.out.println("Egg2.Yolk().f()");
		}
	}
	private Yolk y = new Yolk();
	public Egg2(){
		System.out.println("New Egg2()");
	}
	public void insertYolk(Yolk yy){
		y = yy;
	}
	public void g(){
		y.f();
	}
}
public class BigEgg2 extends Egg2{
	public class Yolk extends Egg2.Yolk{
		public Yolk(){
			System.out.println("BigEgg2.Yolk()");
		}
		public void f(){
			System.out.println("BigEgg2.Yolk.f()");
		}
	}
	public BigEgg2(){
		insertYolk(new Yolk());
	}
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Egg2 e2 = new BigEgg2();
		e2.g();
	}

}
此程式的輸出結果為:


可以看到的是BigEgg2.Yolk通過繼承Egg2.Yolk明確繼承了內部類並覆蓋了其中的方法。

insertYolk方法現在允許BigEgg2將自己的Yolk物件上轉型為Egg2中的引用y,所以當g()呼叫y.f()時,覆蓋後的f()被執行。

第二次呼叫Egg2.Yolk,結果是BigEgg2.Yolk的構建器呼叫了基類的構建器。

輸出結果的前兩行是在建立BigEgg2物件的時候首先對基類進行初始化的結果,第三行是在建立BigEgg2時呼叫基類的insertYolk方法時重新對y進行構建的結果,最後兩行是在用上轉型的Egg2引用操作物件BigEgg2時呼叫繼承過來的內部類方法的結果。