下面的程序?qū)嶋H上不會做任何事情。更糟的是,它連編譯也通不過。為什么呢?又怎么來訂正它呢?
public class Outer {
class Inner1 extends Outer{}
class Inner2 extends Inner1{}
}
這個程序看上去簡單得不可能有錯誤,但是如果你嘗試編譯它,就會得到下面這個有用的錯誤消息:
Outer.java:3: cannot reference this before supertype constructor has been called
class Inner2 extends Inner1{}
好吧,可能這個消息不那么有用,但是我們還是從此入手。問題在于編譯器產(chǎn)生的缺省的Inner2的構(gòu)造器為它的super調(diào)用找不到合適的外部類實例。讓我們來看看顯式地包含了構(gòu)造器的該程序:
public class Outer {
public Outer() {}
class Inner1 extends Outer{
public Inner1() {
super(); // 調(diào)用Object()構(gòu)造器
}
}
class Inner2 extends Inner1{
public Inner2() {
super(); // 調(diào)用Inner1()構(gòu)造器
}
}
}
現(xiàn)在錯誤消息就會顯示出多一點的信息了:
Outer.java:12: cannot reference this before
supertype constructor has been called
super(); // 調(diào)用Inner1()構(gòu)造器
^
因為Inner2的超類本身也是一個內(nèi)部類,一個晦澀的語言規(guī)則登場了。正如大家知道的,要想實例化一個內(nèi)部類,如類Inner1,需要提供一個外部類的實例給構(gòu)造器。一般情況下,它是隱式地傳遞給構(gòu)造器的,但是它也可以以expression.super(args)的方式通過超類構(gòu)造器調(diào)用(superclass constructor invovation)顯式地傳遞[JLS 8.8.7]。如果外部類實例是隱式傳遞的,編譯器會自動產(chǎn)生表達式:它使用this來指代最內(nèi)部的其超類是一個成員變量的外部類。這確實有點繞口,但是這就是編譯器所作的事情。在本例中,那個超類就是Inner1。因為當(dāng)前類Inner2間接擴展了Outer類,Inner1便是它的一個繼承而來的成員。因此,超類構(gòu)造器的限定表達式直接就是this。編譯器提供外部類實例,將super重寫成this.super。 解釋到這里,編譯錯誤所含的意思可擴展為:
Outer.java:12: cannot reference this before
supertype constructor has been called
this.super();
^
現(xiàn)在問題就清楚了:缺省的Inner2的構(gòu)造器試圖在超類構(gòu)造器被調(diào)用前訪問this,這是一個非法的操作[JLS 8.8.7.1]。解決這個問題的蠻力方法是顯式地傳遞合理的外部類實例:
public class Outer {
class Inner1 extends Outer {}
class Inner2 extends Inner1{
public Inner2() {
Outer.this.super();
}
}
}
這樣可以通過編譯,但是它太復(fù)雜了。這里有一個更好的解決方案:無論何時你寫了一個成員類,都要問問你自己,是否這個成員類真的需要使用它的外部類實例?如果答案是否定的,那么應(yīng)該把它設(shè)為靜態(tài)成員類。內(nèi)部類有時是非常有用的,但是它們很容易增加程序的復(fù)雜性,從而使程序難以被理解。它們和泛型(謎題89)、反射(謎題80)以及繼承(本謎題)都有著復(fù)雜的交互方式。在本例中,如果你將Inner1設(shè)為靜態(tài)的便可以解決問題了。如果你將Inner2也設(shè)為靜態(tài)的,你就會真正明白這個程序做了什么:確實是一個相當(dāng)好的意外收獲。
public class Outer {
class Inner1 extends Outer{}
class Inner2 extends Inner1{}
}
這個程序看上去簡單得不可能有錯誤,但是如果你嘗試編譯它,就會得到下面這個有用的錯誤消息:
Outer.java:3: cannot reference this before supertype constructor has been called
class Inner2 extends Inner1{}
好吧,可能這個消息不那么有用,但是我們還是從此入手。問題在于編譯器產(chǎn)生的缺省的Inner2的構(gòu)造器為它的super調(diào)用找不到合適的外部類實例。讓我們來看看顯式地包含了構(gòu)造器的該程序:
public class Outer {
public Outer() {}
class Inner1 extends Outer{
public Inner1() {
super(); // 調(diào)用Object()構(gòu)造器
}
}
class Inner2 extends Inner1{
public Inner2() {
super(); // 調(diào)用Inner1()構(gòu)造器
}
}
}
現(xiàn)在錯誤消息就會顯示出多一點的信息了:
Outer.java:12: cannot reference this before
supertype constructor has been called
super(); // 調(diào)用Inner1()構(gòu)造器
^
因為Inner2的超類本身也是一個內(nèi)部類,一個晦澀的語言規(guī)則登場了。正如大家知道的,要想實例化一個內(nèi)部類,如類Inner1,需要提供一個外部類的實例給構(gòu)造器。一般情況下,它是隱式地傳遞給構(gòu)造器的,但是它也可以以expression.super(args)的方式通過超類構(gòu)造器調(diào)用(superclass constructor invovation)顯式地傳遞[JLS 8.8.7]。如果外部類實例是隱式傳遞的,編譯器會自動產(chǎn)生表達式:它使用this來指代最內(nèi)部的其超類是一個成員變量的外部類。這確實有點繞口,但是這就是編譯器所作的事情。在本例中,那個超類就是Inner1。因為當(dāng)前類Inner2間接擴展了Outer類,Inner1便是它的一個繼承而來的成員。因此,超類構(gòu)造器的限定表達式直接就是this。編譯器提供外部類實例,將super重寫成this.super。 解釋到這里,編譯錯誤所含的意思可擴展為:
Outer.java:12: cannot reference this before
supertype constructor has been called
this.super();
^
現(xiàn)在問題就清楚了:缺省的Inner2的構(gòu)造器試圖在超類構(gòu)造器被調(diào)用前訪問this,這是一個非法的操作[JLS 8.8.7.1]。解決這個問題的蠻力方法是顯式地傳遞合理的外部類實例:
public class Outer {
class Inner1 extends Outer {}
class Inner2 extends Inner1{
public Inner2() {
Outer.this.super();
}
}
}
這樣可以通過編譯,但是它太復(fù)雜了。這里有一個更好的解決方案:無論何時你寫了一個成員類,都要問問你自己,是否這個成員類真的需要使用它的外部類實例?如果答案是否定的,那么應(yīng)該把它設(shè)為靜態(tài)成員類。內(nèi)部類有時是非常有用的,但是它們很容易增加程序的復(fù)雜性,從而使程序難以被理解。它們和泛型(謎題89)、反射(謎題80)以及繼承(本謎題)都有著復(fù)雜的交互方式。在本例中,如果你將Inner1設(shè)為靜態(tài)的便可以解決問題了。如果你將Inner2也設(shè)為靜態(tài)的,你就會真正明白這個程序做了什么:確實是一個相當(dāng)好的意外收獲。