Java內(nèi)部類的異常處理

問題

最近遇到一個問題琳状,使用Java寫某個DSL標記語言X的parser(解析器)Maven插件的時候屉来,對外暴露一個名為Callback的接口和一個待實現(xiàn)的方法getHTML()——基于調(diào)用處傳入的文件名srcX構(gòu)造出HTML文件的輸出路徑(其實此處的Callback就是一個閉包走越,文件名是一個自由變量)浩淘。大致代碼如下:

parser.parse(srcX, new Callback() {
    @Override
    public FileWriter getHTML() {
        return new FileWriter(outputPath(suffix(srcX, "html")));
    }
});

private String suffix(String filename, String suffix) {
    return Joiner.on(".").join(filename, suffix);
}

//這里假設(shè)輸入和輸出根路徑地址已知
private File outputPath(String file) {
    return new File(
               file.replace(srcDir.getAbsolutePath(), //srcDir: File
                 outputDir.getAbsolutePath())); //outputDir: File
}

目前為止還沒有任何問題痘绎。但若是運行時,這段程序很可能拋出異常java.io.FileNotFoundException: your-file-name (No such file or directory)蛉幸。原因在于file的路徑當(dāng)中可能存在多級父級目錄破讨,例如:outputDir/p1/p2/srcX.html,那么當(dāng)FileWriter嘗試創(chuàng)建srcX.html就會失敗奕纫。此時最簡單的方法就是提前創(chuàng)建好所有的父級目錄提陶,于是outputPath()方法會變成下面這樣:

private File outputPath(String file) {
    File outputFile = new File(
            file.replace(srcDir.getAbsolutePath(),
                    outputDir.getAbsolutePath()));
    outputFile.getParentFile().mkdirs(); //創(chuàng)建可能不存在的父級目錄

    return outputFile;
}

似乎這段程序可以正常工作了,但是創(chuàng)建文件夾這樣的操作是可能失敗的匹层。所以我們需要關(guān)注是否創(chuàng)建成功隙笆,若失敗,則寫入Log文件當(dāng)中升筏。修改程序如下:

private File outputPath(String file) {
    File outputFile = new File(
            file.replace(srcDir.getAbsolutePath(),
                    outputDir.getAbsolutePath()));
    final File parentDirs = outputFile.getParentFile();
    if (!parentDirs.exists()) {
        if (!parentDirs.mkdirs()) {//創(chuàng)建可能不存在的父級目錄
            getLog().error("Cannot create parent dirs for {}", outputFile);
        }
    }
    return outputFile;
}

注意
這里我們需要先判斷父級目錄是否存在撑柔,即parentDirs.exists()?可是parentDirs.mkdirs()不是直接返回boolean值來表示是否創(chuàng)建成功嗎您访?是這樣么铅忿?這兒有mkdirs()方法的說明:

public boolean mkdirs()
Creates the directory named by this abstract pathname, including any necessary but nonexistent parent directories. Note that if this operation fails it may have succeeded in creating some of the necessary parent directories.
Returns:
true if and only if the directory was created, along with all necessary parent directories; false otherwise

也就是說只有當(dāng)這個目錄及其所有的父級目錄都被創(chuàng)建時,才返回true灵汪,反之返回false檀训。照這個推論,如果所有目錄事先已經(jīng)存在了享言,這個方法應(yīng)該也會返回true峻凫,畢竟都被創(chuàng)建過了嘛。但是只要稍微看一眼源碼担锤,你就會發(fā)現(xiàn)事實并非如此:

//mkdirs源碼
if (exists()) {
    return false;
}

所以這里需要特別強調(diào)was created是一種操作蔚晨,如果沒有進行這個操作,那就不能算這個方法成功肛循。

前面已經(jīng)提到過,我需要寫一個maven的插件银择,所以最好在這種導(dǎo)致程序崩潰的地方拋出一個maven中通用的異常MojoExecutionException多糠。這樣,更改代碼如下:

private File outputPath(String file) {
    File outputFile = new File(
            file.replace(srcDir.getAbsolutePath(),
                    outputDir.getAbsolutePath()));
    final File parentDirs = outputFile.getParentFile();
    if (!parentDirs.exists()) {
        if (!parentDirs.mkdirs()) {//創(chuàng)建可能不存在的父級目錄
            getLog().error("Cannot create parent dirs for {}", outputFile);
            throw new MojoExecutionException("Cannot create parent dirs");
        }
    }
    return outputFile;
}

此時浩考,問題才顯出端倪——異常MojoExecutionException是一個受檢的異常(checked Exception)夹孔,它間接繼承自java.lang.Exception。可是我們的getHTML()方法并沒有在簽名中拋出任何異常搭伤,編譯無法通過只怎。那唯一的辦法就是try...catch了,但是我不應(yīng)該捕獲自己剛剛拋出來的異常怜俐,否則拋出受檢異常的意義何在身堡?

這時,自然而然會想到拍鲤,將方法簽名改成getHTML() throws MojoExecutionException贴谎。確實可行,但是并不合適季稳,因為MojoExecutionException只是Maven插件規(guī)定的異常擅这,而getHTML()則是一個對外暴露的API,不應(yīng)該依賴于某個具體的異常景鼠。所以我將異常擴大化:getHTML() throws Exception仲翎,這樣做的好處很明顯,壞處也很顯眼铛漓。

好處

  1. 牢記《Unix編程藝術(shù)》中的“寬收嚴發(fā)”原則溯香。即子類實現(xiàn)父類、接口的方法票渠,入?yún)⒖梢詳U大逐哈,出參可以縮小。舉個例子:父類问顷、接口有個方法
public Object something(HashMap map) throws Exception

那么子類實現(xiàn)這個方法可以這樣寫

public String something(Map map)
                  throws ExecutionException, NoSuchMethodException

這里昂秃,入?yún)⑹荋ashMap,出參是Object和Exception杜窄。入?yún)U大肠骆,所以子類出現(xiàn)了Map;出參縮小塞耕,所以子類出現(xiàn)了String和ExecutionException和NoSuchMethodException蚀腿。同理,此處getHTML() throws Exception由子類實現(xiàn)的形式可以是getHTML() throws MojoExecutionException扫外。

壞處

  1. 不管getHTML()是否需要拋出異常莉钙,你都得在實現(xiàn)代碼中拋出異常;
  2. 由于對外表現(xiàn)的是拋出較寬泛的Exception筛谚,所以喪失了對于具體受檢 (checked exception)異常進行檢查的好處磁玉。

這里有個JDK中比較類似的例子,就是關(guān)于RunnableCallable接口的設(shè)計問題:

public interface Runnable {
    public void run();
}

public interface Callable<V> {
    V call() throws Exception;
}

它們就是兩個極端驾讲,Runnable必須將受檢的異常轉(zhuǎn)換成非受檢(unchecked exception)或者發(fā)明一種方式來將異常暴露給調(diào)用者蚊伞;Callable就是無論如何都得拋出異常席赂,而且迫使用戶去捕獲一個較寬泛的異常。

解決方式

這個時候时迫,泛型就派上用場了颅停。

interface Callback<E extends Exception> {
    FileWriter getHTML() throws E;
}

//interface parser
public <E extends Exception> void parse(String srcX, Callback<E> cb) throws E;

通過這種方式,我們可以捕獲具體的異常:

try {
    parser.parse(srcX, new Callback<MojoExecutionException>() {
        @Override
        public FileWriter getHTML() throws MojoExecutionException {
            return new FileWriter(outputPath(suffix(srcX, "html")));
        }
    });
} catch (MojoExecutionException e) {
    getLog().error("Failed to execute. {}", e);
}

使用lambda表達式可以簡化成下面的模樣:

try {
    parser.parse(srcX, (Callback<MojoExecutionException>) () -> new FileWriter(outputPath(suffix(srcX, "html"))));
} catch (MojoExecutionException e) {
    getLog().error("Failed to execute. {}", e);
}

我們解決了迫使用戶去捕獲一個較寬泛的異常的問題掠拳,但是無論如何都得拋出異常這個問題還是沒有得到解決癞揉。或許我們需要一個像是throws Nothing一樣的語法碳想,表示什么也沒有拋出來烧董。我們知道RuntimeException是非受檢的異常(unchecked exception),所以throws RuntimeException就表明這個異常跟沒有拋出異常一樣胧奔,不需要捕獲逊移。如下:

parser.parse(srcX, new Callback<Nothing>() {
    @Override
    public FileWriter getHTML() throws Nothing {
        return new FileWriter(outputPath(suffix(srcX, "html")));
    }
});

public abstract class Nothing extends RuntimeException {}

走到這一步,我們算是較為完全地解決了匿名內(nèi)部類的異常處理問題龙填。

異常透明化

With the throws type parameter on the Block interface, we can now accurately generify over the set of exceptions thrown by the Block; with the generic forEach method, we can mirror the exception behavior of the block in forEach(). This is called exception transparency because now the exception behavior of forEach can match the exception behavior of its block argument. Exception transparency simplifies the construction of library classes that implement idioms like internal iteration of data structures, because it is common that methods that accept function-valued arguments will invoke those functions, meaning that the library method will throw a superset of the exceptions thrown by its function-valued arguments.

 interface Block<T, throws E> {
     public void invoke(T element) throws E;
 }

 interface NewCollection<T> {
     public<throws E> forEach(Block<T, throws E> block) throws E;
 }

異常透明化胳泉,簡單來講,就是調(diào)用者的簽名中的異常完全由它的函數(shù)值(function-valued)的參數(shù)決定岩遗,所有這些調(diào)用者最終的異常都會是該函數(shù)值所注異常的超集扇商。

異常透明化就是用來解決我們常用的通過內(nèi)部類模擬閉包調(diào)用時異常處理的手法了。


閉包的定義
一個包含了自由變量的開發(fā)表達式宿礁,和該自由變量的約束環(huán)境組合之后案铺,產(chǎn)生了一種封閉的狀態(tài)。


參考鏈接
[1] Exception Transparency
[2] Throwing Checked Exceptions from Anonymous Inner Classes

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末梆靖,一起剝皮案震驚了整個濱河市控汉,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌返吻,老刑警劉巖姑子,帶你破解...
    沈念sama閱讀 217,657評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異测僵,居然都是意外死亡街佑,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評論 3 394
  • 文/潘曉璐 我一進店門捍靠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來沐旨,“玉大人,你說我怎么就攤上這事榨婆∠A” “怎么了?”我有些...
    開封第一講書人閱讀 164,057評論 0 354
  • 文/不壞的土叔 我叫張陵纲辽,是天一觀的道長颜武。 經(jīng)常有香客問我,道長拖吼,這世上最難降的妖魔是什么鳞上? 我笑而不...
    開封第一講書人閱讀 58,509評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮吊档,結(jié)果婚禮上篙议,老公的妹妹穿的比我還像新娘。我一直安慰自己怠硼,他們只是感情好鬼贱,可當(dāng)我...
    茶點故事閱讀 67,562評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著香璃,像睡著了一般这难。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上葡秒,一...
    開封第一講書人閱讀 51,443評論 1 302
  • 那天姻乓,我揣著相機與錄音,去河邊找鬼眯牧。 笑死蹋岩,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的学少。 我是一名探鬼主播剪个,決...
    沈念sama閱讀 40,251評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼版确!你這毒婦竟也來了扣囊?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,129評論 0 276
  • 序言:老撾萬榮一對情侶失蹤阀坏,失蹤者是張志新(化名)和其女友劉穎如暖,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體忌堂,經(jīng)...
    沈念sama閱讀 45,561評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡盒至,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,779評論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了士修。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片枷遂。...
    茶點故事閱讀 39,902評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖棋嘲,靈堂內(nèi)的尸體忽然破棺而出酒唉,到底是詐尸還是另有隱情,我是刑警寧澤沸移,帶...
    沈念sama閱讀 35,621評論 5 345
  • 正文 年R本政府宣布痪伦,位于F島的核電站侄榴,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏网沾。R本人自食惡果不足惜癞蚕,卻給世界環(huán)境...
    茶點故事閱讀 41,220評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望辉哥。 院中可真熱鬧桦山,春花似錦、人聲如沸醋旦。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,838評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽饲齐。三九已至钉凌,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間箩张,已是汗流浹背甩骏。 一陣腳步聲響...
    開封第一講書人閱讀 32,971評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留先慷,地道東北人饮笛。 一個月前我還...
    沈念sama閱讀 48,025評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像论熙,于是被迫代替她去往敵國和親福青。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,843評論 2 354

推薦閱讀更多精彩內(nèi)容

  • 通俗編程——白話JAVA異常機制 - 代碼之道脓诡,編程之法 - 博客頻道 - CSDN.NEThttp://blog...
    葡萄喃喃囈語閱讀 3,179評論 0 25
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法无午,類相關(guān)的語法,內(nèi)部類的語法祝谚,繼承相關(guān)的語法宪迟,異常的語法,線程的語...
    子非魚_t_閱讀 31,631評論 18 399
  • 六種異常處理的陋習(xí) 你覺得自己是一個Java專家嗎交惯?是否肯定自己已經(jīng)全面掌握了Java的異常處理機制次泽?在下面這段代...
    Executing閱讀 1,327評論 0 6
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)席爽,斷路器意荤,智...
    卡卡羅2017閱讀 134,656評論 18 139
  • 那一年我十七歲胁孙,在匆匆而過的青春里阻肿,留下了一道道傷痕。這時的我們挣输,已經(jīng)失去了年少時的勇氣齐饮,卻也漸漸的明白捐寥,那份曾被...
    我們的丶天空閱讀 409評論 0 0