JAVA異常謎題43:異常地危險(xiǎn)(1)

字號(hào):

在JDK1.2中,Thread.stop、Thread.suspend以及其他許多線程相關(guān)的方法都因?yàn)樗鼈儾话踩煌扑]使用了[ThreadStop]。下面的方法展示了你用Thread.stop可以實(shí)現(xiàn)的可怕事情之一。
     // Don’t do this - circumvents exception checking!
     public static void sneakyThrow(Throwable t) {
     Thread.currentThread().stop(t); // Deprecated!!
     }
    這個(gè)討厭的小方法所做的事情正是throw語(yǔ)句要做的事情,但是它繞過(guò)了編譯器的所有異常檢查操作。你可以(卑鄙地)在你的代碼的任意一點(diǎn)上拋出任何受檢查的或不受檢查的異常,而編譯器對(duì)此連眉頭都不會(huì)皺一下。
    不使用任何不推薦的方法,你也可以編寫(xiě)出在功能上等價(jià)于sneakyThrow的方法。事實(shí)上,至少有兩種方式可以這么實(shí)現(xiàn)這一點(diǎn),其中一種只能在5.0或更新的版本中運(yùn)行。你能夠編寫(xiě)出這樣的方法嗎?它必須是用Java而不是用JVM字節(jié)碼編寫(xiě)的,你不能在其客戶(hù)對(duì)它編譯完之后再去修改它。你的方法不必是完美無(wú)瑕的:如果它不能拋出一兩個(gè)Exception的子類(lèi),也是可以接受的。
    本謎題的一種解決之道是利用Class.newInstance方法中的設(shè)計(jì)缺陷,該方法通過(guò)反射來(lái)對(duì)一個(gè)類(lèi)進(jìn)行實(shí)例化。引用有關(guān)該方法的文檔中的話[Java-API]:“請(qǐng)注意,該方法將傳播從空的[換句話說(shuō),就是無(wú)參數(shù)的]構(gòu)造器所拋出的任何異常,包括受檢查的異常。使用這個(gè)方法可以有效地繞開(kāi)在其他情況下都會(huì)執(zhí)行的編譯期異常檢查?!币坏┠懔私饬诉@一點(diǎn),編寫(xiě)一個(gè)sneakyThrow的等價(jià)方法就不是太難了。
    public class Thrower {
     private static Throwable t;
     private Thrower() throws Throwable {
     throw t;
     }
    public static synchronized void sneakyThrow(Throwable t) {
     Thrower.t = t;
     try {
     Thrower.class.newInstance();
     } catch (InstantiationException e) {
     throw new IllegalArgumentException();
     } catch (IllegalAccessException e) {
     throw new IllegalArgumentException();
     } finally {
     Thrower.t = null; // Avoid memory leak
     }
     }
    }
    在這個(gè)解決方案中將會(huì)發(fā)生許多微妙的事情。我們想要在構(gòu)造器執(zhí)行期間所拋出的異常不能作為一個(gè)參數(shù)傳遞給該構(gòu)造器,因?yàn)镃lass.newInstance調(diào)用的是一個(gè)類(lèi)的無(wú)參數(shù)構(gòu)造器。因此,sneakyThrow方法將這個(gè)異常藏匿于一個(gè)靜態(tài)變量中。為了使該方法是線程安全的,它必須被同步,這使得對(duì)其的并發(fā)調(diào)用將順序地使用靜態(tài)域t。
    要注意的是,t這個(gè)域在從finally語(yǔ)句塊中出來(lái)時(shí)是被賦為空的:這只是因?yàn)樵摲椒m然是卑鄙的,但這并不意味著它還應(yīng)該是內(nèi)存泄漏的。如果這個(gè)域不是被賦為空出來(lái)的,那么它阻止該異常被垃圾回收。最后,請(qǐng)注意,如果你讓該方法拋出一個(gè)InstantiationException或是一個(gè)IllegalAccessException異常,它將以拋出一個(gè)IllegalArgumentException而失敗。這是這項(xiàng)技術(shù)的一個(gè)內(nèi)在限制。
    Class.newInstance的文檔繼續(xù)描述道“Constructor.newInstance方法通過(guò)將構(gòu)造器拋出的任何異常都包裝在一個(gè)(受檢查的)InvocationTargetException異常中而避免了這個(gè)問(wèn)題?!焙苊黠@,Class.newInstance應(yīng)該是做了相同的處理,但是糾正這個(gè)缺陷已經(jīng)為時(shí)過(guò)晚,因?yàn)檫@么做將引入源代碼級(jí)別的不兼容性,這將使許多依賴(lài)于Class.newInstance的程序崩潰。而棄用這個(gè)方法也不切實(shí)際,因?yàn)樗S昧?。?dāng)你在使用它時(shí),一定要意識(shí)到Class.newInstance可以拋出它沒(méi)有聲明過(guò)的受檢查異常。
    被添加到5.0版本中的“通用類(lèi)型(generics)”可以為本謎題提供一個(gè)完全不同的解決方案。為了實(shí)現(xiàn)的兼容性,通用類(lèi)型是通過(guò)類(lèi)型擦除(type erasure)來(lái)實(shí)現(xiàn)的:通用類(lèi)型信息是在編譯期而非運(yùn)行期檢查的[JLS 4.7]。