Java高級(jí)謎題90:荒謬痛苦的超類(lèi)

字號(hào):

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