JAVA異常謎題40:不情愿的構(gòu)造器

字號(hào):

盡管在一個(gè)方法聲明中看到一個(gè)throws子句是很常見(jiàn)的,但是在構(gòu)造器的聲明中看到一個(gè)throws子句就很少見(jiàn)了。下面的程序就有這樣的一個(gè)聲明。那么,它將打印出什么呢?
    public class Reluctant {
     private Reluctant internalInstance = new Reluctant();
     public Reluctant() throws Exception {
     throw new Exception("I’m not coming out");
     }
     public static void main(String[] args) {
     try {
     Reluctant b = new Reluctant();
     System.out.println("Surprise!");
     } catch (Exception ex) {
     System.out.println("I told you so");
     }
     }
    }
    main方法調(diào)用了Reluctant構(gòu)造器,它將拋出一個(gè)異常。你可能期望catch子句能夠捕獲這個(gè)異常,并且打印I told you so。湊近仔細(xì)看看這個(gè)程序就會(huì)發(fā)現(xiàn),Reluctant實(shí)例還包含第二個(gè)內(nèi)部實(shí)例,它的構(gòu)造器也會(huì)拋出一個(gè)異常。無(wú)論拋出哪一個(gè)異常,看起來(lái)main中的catch子句都應(yīng)該捕獲它,因此預(yù)測(cè)該程序?qū)⒋蛴 told you應(yīng)該是一個(gè)安全的賭注。但是當(dāng)你嘗試著去運(yùn)行它時(shí),就會(huì)發(fā)現(xiàn)它壓根沒(méi)有去做這類的事情:它拋出了StackOverflowError異常,為什么呢?
    與大多數(shù)拋出StackOverflowError異常的程序一樣,本程序也包含了一個(gè)無(wú)限遞歸。當(dāng)你調(diào)用一個(gè)構(gòu)造器時(shí),實(shí)例變量的初始化操作將先于構(gòu)造器的程序體而運(yùn)行[JLS 12.5]。在本謎題中, internalInstance變量的初始化操作遞歸調(diào)用了構(gòu)造器,而該構(gòu)造器通過(guò)再次調(diào)用Reluctant構(gòu)造器而初始化該變量自己的internalInstance域,如此無(wú)限遞歸下去。這些遞歸調(diào)用在構(gòu)造器程序體獲得執(zhí)行機(jī)會(huì)之前就會(huì)拋出StackOverflowError異常,因?yàn)镾tackOverflowError是Error的子類型而不是Exception的子類型,所以catch子句無(wú)法捕獲它。
    對(duì)于一個(gè)對(duì)象包含與它自己類型相同的實(shí)例的情況,并不少見(jiàn)。例如,鏈接列表節(jié)點(diǎn)、樹(shù)節(jié)點(diǎn)和圖節(jié)點(diǎn)都屬于這種情況。你必須非常小心地初始化這樣的包含實(shí)例,以避免StackOverflowError異常。
    至于本謎題名義上的題目:聲明將拋出異常的構(gòu)造器,你需要注意,構(gòu)造器必須聲明其實(shí)例初始化操作會(huì)拋出的所有被檢查異常。下面這個(gè)展示了常見(jiàn)的“服務(wù)提供商”模式的程序,將不能編譯,因?yàn)樗`反了這條規(guī)則:
    public class Car {
     private static Class engineClass = ...;
     private Engine engine =
     (Engine)enginClass.newInstance();
     public Car(){ }
    }
    盡管其構(gòu)造器沒(méi)有任何程序體,但是它將拋出兩個(gè)被檢查異常,InstantiationException和IllegalAccessException。它們是Class.Instance拋出的,該方法是在初始化engine域的時(shí)候被調(diào)用的。訂正該程序的方式是創(chuàng)建一個(gè)私有的、靜態(tài)的助手方法,它負(fù)責(zé)計(jì)算域的初始值,并恰當(dāng)?shù)靥幚懋惓?。在本案中,我們假設(shè)選擇engineClass所引用的Class對(duì)象,保證它是可訪問(wèn)的并且是可實(shí)例化的。
    下面的Car版本將可以毫無(wú)錯(cuò)誤地通過(guò)編譯:
    //Fixed - instance initializers don’t throw checked exceptions
    public class Car {
     private static Class engineClass = ...;
     private Engine engine = newEngine;
     private static Engine newEngine() {
     try {
     return (Engine)engineClass.newInstance();
     } catch (IllegalAccessException e) {
     throw new AssertionError(e);
     } catch (InstantiationException e) {
     throw new AssertionError(e);
     }
     }
     public Car(){ }
    }
    總之,實(shí)例初始化操作是先于構(gòu)造器的程序體而運(yùn)行的。實(shí)例初始化操作拋出的任何異常都會(huì)傳播給構(gòu)造器。如果初始化操作拋出的是被檢查異常,那么構(gòu)造器必須聲明也會(huì)拋出這些異常,但是應(yīng)該避免這樣做,因?yàn)樗鼤?huì)造成混亂。最后,對(duì)于我們所設(shè)計(jì)的類,如果其實(shí)例包含同樣屬于這個(gè)類的其他實(shí)例,那么對(duì)這種無(wú)限遞歸要格外當(dāng)心。