JAVA中內部類的繼承和覆蓋問題
在JAVA中,由於內部類的構建器必須連線到指向其外圍類物件的引用,所以在繼承內部類的時候會變得很複雜。問題在於指向外圍類物件的祕密引用必須得到初始化,而在匯出類中不再存在可連線的預設物件,我們必須使用特殊的語法來明確說明它們的關係:
enclosingClassReference.super()。
看如下一段程式碼:
可以看到InheritInner只繼承了內部類,並沒有繼承外圍類,但若要生成一個構建器時,預設的構建器並不能完成此工作,不能只是傳遞一個指向外圍類物件的引用,必須提供上述語法來確定一個關係。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); } }
如果我們建立了一個內部類,然後繼承其外圍類並重新定義內部類的時候會發生什麼?看上去這和覆蓋某種方法類似,但實際則不然,覆蓋內部類就好像是外圍類的一個方法,實際上並不起什麼作用,看如下一段程式碼:
此程式的執行結果為: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時呼叫繼承過來的內部類方法的結果。