Java進(jìn)階——詳談Exception

寫(xiě)在最前

最近筆者在撰寫(xiě)JavaWeb與自動(dòng)化相結(jié)合的教程,上篇入口在這里,第二篇還在創(chuàng)作中,在發(fā)布之前,讓我們先來(lái)討論一個(gè)Java的重要技能,Exception。

實(shí)現(xiàn)程序的運(yùn)行是所有初級(jí)的程序員所追求的,Thinking in Java 因此成為了很適合入門(mén)的一本書(shū),然而隨著代碼行數(shù)的累積,越來(lái)越多的坑也隨之到來(lái)。此時(shí),對(duì)基礎(chǔ)知識(shí)更深層次的理解就尤為關(guān)鍵。在JavaWeb與自動(dòng)化結(jié)合的應(yīng)用中,無(wú)腦拋出異常會(huì)導(dǎo)致代碼的冗余與羸弱,今天發(fā)的這篇文章將仔細(xì)地對(duì)Exception的運(yùn)用進(jìn)行分析。

需要注意的是,本篇文章并不是對(duì)如何拋出異常的基礎(chǔ)進(jìn)行講解,需要讀者對(duì)Exception機(jī)制有一定了解,文中部分用例來(lái)自Effective Java,在這里同時(shí)向讀者推薦這本書(shū)作為Java進(jìn)階的重要工具,文末附錄中有筆者Exception部分的英文筆記供大家參考。

使用Exception的情景

不要在類(lèi)似迭代的循環(huán)中使用Exception,尤其是涉及ArrayIndexOutOfBounds,如下所示:

try {
    int i = 0;
    while(true)
        array[i++].doSomething();
} catch(ArrayIndexOUtOfBoundsException e) {

}

主要因?yàn)榇藭r(shí)使用try-catch有三點(diǎn)顯而易見(jiàn)的壞處:

  • 這樣做違背于JVM設(shè)置exception處理的原則,JVM會(huì)花費(fèi)更多的時(shí)間來(lái)處理。
  • 把Code放在try-catch語(yǔ)句中使得一些JVM運(yùn)行中的優(yōu)化被封禁。
  • 規(guī)范的迭代寫(xiě)法是經(jīng)過(guò)優(yōu)化的,通過(guò)JVM的內(nèi)部處理,避免了很多贅余的檢查機(jī)制,是更合適的選擇。

如果我們?cè)趖ry-catch語(yǔ)句中調(diào)用了另一個(gè)數(shù)組,這個(gè)數(shù)組中出現(xiàn)了ArrayIndexOutOfBounds的異常,其中的bug就會(huì)被catch exception所蒙蔽。相反,標(biāo)準(zhǔn)的迭代寫(xiě)法會(huì)及時(shí)的終止線程的執(zhí)行,報(bào)出錯(cuò)誤并且給出追蹤錯(cuò)誤的路徑讓程序員更輕松地定位bug的來(lái)源。

現(xiàn)在我們通過(guò)Java的Iterator接口來(lái)看一下標(biāo)準(zhǔn)迭代寫(xiě)法,在標(biāo)準(zhǔn)的迭代寫(xiě)法中,我們利用hasNext()作為state-testing判斷方法,來(lái)實(shí)現(xiàn)state-dependent方法next(),代碼如下:

for (Iterator<Foo> i = collection.iterator(); i.hasNext(); ) {
    Foo foo = i.next();
    //...
}

綜上所述,Exception是為了異?;蛘哒f(shuō)例外的情況而準(zhǔn)備的,不應(yīng)該在普通的語(yǔ)句中使用,并且程序員也不該寫(xiě)出強(qiáng)迫他人在正常流程的語(yǔ)句中使用Exception的API。

Checked與Unchecked的區(qū)別

在Java中Throwable是Exception與Error的父類(lèi),而在Effective Java書(shū)中,Throwable被分為了以下三類(lèi):

  1. Checked Exceptions
  2. Runtime Exceptions
  3. Errors

其中,2和3都是Unchecked Throwable,所以在我們分析Java的異常類(lèi)時(shí),從Checked與Unchecked兩個(gè)邏輯角度來(lái)分析會(huì)更加清晰。

Checked Exception指那些在編譯過(guò)程中會(huì)檢查的,這類(lèi)錯(cuò)誤在運(yùn)行中是“可恢復(fù)的”。我們需要在寫(xiě)程序時(shí)將其拋出,換而言之,這些異常應(yīng)該并不是由程序員所導(dǎo)致,而是類(lèi)似”例行檢查“。

相反,Runtime Exception指的就是程序員本身制造出來(lái)的錯(cuò)誤,在文章的第一部分中我們已經(jīng)明確指出,此類(lèi)錯(cuò)誤不應(yīng)該被拋出,而應(yīng)該由程序員自己去修復(fù)。需要注意的是,一般來(lái)說(shuō),我們自己設(shè)計(jì)的Exception應(yīng)該作為Runtime Exception的直接或者間接子類(lèi)。如果你對(duì)Exception理解得比較淺,暴力地把Runtime Exception的子類(lèi)背下來(lái),對(duì)debug的幫助也相當(dāng)大,可以快速定位代碼中的問(wèn)題。

Errors與Exception不同,他是與JVM相關(guān)的,當(dāng)你在寫(xiě)算法時(shí)看到棧溢出,那并不是你對(duì)語(yǔ)言的理解導(dǎo)致你的代碼出現(xiàn)漏洞,而是你的數(shù)據(jù)結(jié)構(gòu)使得JVM出現(xiàn)resource deficiency或invariant failures使得程序無(wú)法繼續(xù)執(zhí)行,所以看到Errors的時(shí)候,我們也不應(yīng)將其拋出,而是應(yīng)該對(duì)代碼結(jié)構(gòu)進(jìn)行修改處理。

綜上所述,如果在運(yùn)行中可恢復(fù),那么我們就應(yīng)該將這種Checked Exception拋出。當(dāng)不清楚該如何做的時(shí)候,拋出Runtime Exception。重要的是,不要定義既不是Checked Exception子類(lèi)也不是Runtime Exception子類(lèi)的Throwable,并且記得在你自定義的Checked Exception中加入方法使代碼能在運(yùn)行中恢復(fù)。

Checked Exception的使用技巧

我們經(jīng)常會(huì)遇到這種問(wèn)題,在一個(gè)方法中,有一行代碼需要拋出Exception,我們需要將他包裹在try-catch語(yǔ)句中。在Java8之后,我們?cè)谑褂么薃PI時(shí)必須拋出這個(gè)異常,這極大地降低了我們代碼的質(zhì)量。

解決這個(gè)問(wèn)題最簡(jiǎn)單的方法可能就是我們?cè)谶\(yùn)行此方法是不返回任何值,但是如果這樣做我們就少了很多通過(guò)此方法返回信息和數(shù)據(jù)的機(jī)會(huì)。

因此我們提供了另一種解決方式,那便是通過(guò)將需要拋出Checked Exception的方法拆為兩個(gè)方法,使其轉(zhuǎn)變?yōu)橐粋€(gè)Unchecked Exception。第一個(gè)方法通過(guò)返回一個(gè)boolean值來(lái)指明此Exception是否應(yīng)該被拋出,第二個(gè)再進(jìn)行剩余的操作。下面是一個(gè)轉(zhuǎn)變的簡(jiǎn)單例子。

包裹在try-catch中的語(yǔ)句:

try {
    ted.read(book);
} catch (CheckedException e) {
    //...do sth.
}

下面是改造后的代碼:

if (ted.understand(book)) {
    ted.read(book);
} else {
    //...do sth.
}

簡(jiǎn)單來(lái)說(shuō),就是本來(lái)是再Ted”讀“這個(gè)方法中拋出他看不懂這個(gè)書(shū)的異常,但我們將其拆分為”是否理解“與“讀”兩個(gè)方法對(duì)其進(jìn)行重構(gòu),來(lái)避免try-catch的運(yùn)用。

總的來(lái)說(shuō),重構(gòu)Checked Exception是為了代碼更簡(jiǎn)潔更可靠,避免了對(duì)Checked Exception的過(guò)度使用,因?yàn)檫^(guò)度使用會(huì)導(dǎo)致API對(duì)使用者很不友好。在遇到上面所說(shuō)的情況時(shí),首先考慮能否使用返回值為空的方法,因?yàn)檫@是最直接最簡(jiǎn)單的解決方式。

優(yōu)先使用標(biāo)準(zhǔn)庫(kù)中的Exception

使用Java庫(kù)中提供地Exception有三大好處:

  1. 使你的API更容易地被學(xué)習(xí)與使用,因?yàn)榇蠖鄶?shù)程序員都了解標(biāo)準(zhǔn)的異常
  2. 讓使用了你的API的程序閱讀起來(lái)更輕松
  3. 更少地占用內(nèi)存并且更快地對(duì)Class進(jìn)行加載(JVM)

不要直接重用Exception, RuntimeException, Throwable或是Error這些父類(lèi),常用的Exception在下表中列出。

Exception 使用場(chǎng)景
IllegalArgumentException 不匹配的非空參數(shù)的傳遞
IllegalStateException 未初始化的對(duì)象(對(duì)象狀態(tài)不匹配)
NullPointerException 在未預(yù)期的情況下遭遇空指針
IndexOutOfBoundsException 索引參數(shù)超出范圍
ConcurrentModificationException 多線程對(duì)同一個(gè)對(duì)象進(jìn)行修改
UnsupportedOperationException 此對(duì)象不支持對(duì)此方法的引用

需要注意的是,重用的Exception一定要與記錄的語(yǔ)義一致,在文檔中詳細(xì)說(shuō)明,并不只是簡(jiǎn)單地匹配Exception的名字。

結(jié)語(yǔ)

除了上面詳述的幾點(diǎn)外,還要注意的是,首先,每個(gè)方法拋出的異常都要有文檔。其次,保持異常的原子性。最重要的是,千萬(wàn)不要在catch中忽略掉捕獲到的異常。

關(guān)于異常處理對(duì)于很多人來(lái)說(shuō)只是Alt+Enter,但是在代碼優(yōu)化階段經(jīng)常很讓人頭疼,希望本文能使大家有所啟發(fā),對(duì)于接下來(lái)教程中的一些代碼有更好的理解,也歡迎大家提問(wèn),共同提高。

附錄:Effective Java 讀書(shū)筆記

Chapter 10 EXCEPTIONS

Item 69: Use exceptions only for exceptional conditions

Do not use try catch to handle your loop, it might mask the bug and is also very slow.

Exceptions are, as their name implies, to be used only for exceptional conditions; they should never be used for ordinary control flow and do not write APIs that force others to do so.

A well designed API must not force its clients to use exceptions for ordinary control flow.

In iteration codes, one should use hasNext() to decide the life circle of a loop.

Item 70: Use checked exceptions for recoverable conditions and runtime exceptions for programming errors

Use checked exceptions for conditions from which the caller can reasonably be expected to recover.

Use runtime exceptions to indicate programming errors.

All of the unchecked throwables you implement should subclass RuntimeException (directly or indirectly).

Don't define any throwables that are neither checked exceptions nor runtime exceptions.

Provide methods on your checked exceptions to aid in recovery.

Item 71: Avoid unnecessary use of checked exceptions

In Java 8, methods throwing checked exceptions can't be used directly in streams.

How to solve the problem that if a method throws a single checked exception, this exception is the sole reason the method must appear in a try block and can't be used directly in streams?

The easiest way to eliminate this is to return an optional of the desired result type.

You can also turn a checked exception into an unchecked exception by breaking the method that throws the exception into two methods, the first of which returns a boolean indicating whether the exception would be thrown.

Item 72: Favor the use of standard exceptions

The Java libraries provide a set of exceptions that covers most of the exceptions-throwing needs of most APIs.

Benefits: makes your API easier to learn because it matches the established conventions, makes programs using your API easier to read, a smaller memory footprint and less time spent loading classes.

Do not reuse Exception, RuntimeException, Throwable, or Error directly.

Reuse must be based on documented semantics, not just on name.

Item 73: Throw exceptions appropriate to the abstraction

Higher layers should catch lower-level exceptions and, in their place, throw exceptions that can be explained in terms of the higher-level abstraction, aka. Exception Translation.

While exception translation is superior to mindless propagation of exceptions from lower layers, it should not be overused.

If it is not feasible to prevent or to handle exceptions from lower layers, use exception translation, unless the lower-level method happens to guarantee that all of its exceptions are appropriate to the higher level.

Item 74: Document all exceptions thrown by each method

Always declare checked exceptions individually, and dovument precisely the conditions under which each one is thrown using @throws tag.

Use the Javadoc @throws tag to document each exception that a method can throw, but do not use the throws keyword on unchecked exceptions.

If an exception is thrown by many methods in a class for the same reason, you can document the exception in the class's documentation comment.

Item 75: Include failure-capture information in detail messages

To capture a failure, the detail message of an exception should contain the values of all parameters and fields that contributed to the exception.

Do not include passwords, encryption keys, and the like in detail messages.

Item 76: Strive for failure atomicity

A failed method invocation should leave the object in the state that it was in prior to the invocation.

Item 77: Don't ignore exceptions

An empty catch block defeats the purpose of exceptions.

If you choose to ignore an exception, the catch block should contain a comment explaining why it is appropriate to do so, and the variable should be named ignored.

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • rljs by sennchi Timeline of History Part One The Cognitiv...
    sennchi閱讀 7,847評(píng)論 0 10
  • mean to add the formatted="false" attribute?.[ 46% 47325/...
    ProZoom閱讀 3,184評(píng)論 0 3
  • 晨時(shí), 猶欲還雨, 單坐里,問(wèn)時(shí)雨下。 三兩滴秋水穿窗落地,幾處生花。 步入中庭, 怎知他,乍還暖, 只得觀雨...
    華尊依月閱讀 317評(píng)論 2 10
  • 俄羅斯花樣滑冰選手米哈伊爾·科爾亞達(dá)接受采訪表示,不用因?yàn)楣?jié)目中的錯(cuò)誤而感到失望,負(fù)分也是分?jǐn)?shù)。 “我明白我們?cè)阱e(cuò)...
    云游四方的旅人閱讀 491評(píng)論 0 0
  • 今天幫丫頭聽(tīng)寫(xiě)生字,我看她寫(xiě)的字還可以??伤床贿^(guò)眼,都用橡皮擦了重新寫(xiě)了一遍。要求真嚴(yán)格啊。孩子,為你的態(tài)度點(diǎn)贊。
    91b6f8355762閱讀 172評(píng)論 0 1

友情鏈接更多精彩內(nèi)容