Java進階之詳談Exception

寫在最前

最近筆者在撰寫JavaWeb與自動化相結合的教程,上篇入口在這裏,第二篇還在創做中,在發佈以前,讓咱們先來討論一個Java的重要技能,Exception。java

實現程序的運行是全部初級的程序員所追求的,Thinking in Java 所以成爲了很適合入門的一本書,然而隨着代碼行數的累積,愈來愈多的坑也隨之到來。此時,對基礎知識更深層次的理解就尤其關鍵。在JavaWeb與自動化結合的應用中,無腦拋出異常會致使代碼的冗餘與羸弱,今天發的這篇文章將仔細地對Exception的運用進行分析。程序員

須要注意的是,本篇文章並非對如何拋出異常的基礎進行講解,須要讀者對Exception機制有必定了解,文中部分用例來自Effective Java,在這裏同時向讀者推薦這本書做爲Java進階的重要工具,文末附錄中有筆者Exception部分的英文筆記供你們參考。算法

使用Exception的情景

不要在相似迭代的循環中使用Exception,尤爲是涉及ArrayIndexOutOfBounds,以下所示:數組

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

}
複製代碼

主要由於此時使用try-catch有三點顯而易見的壞處:數據結構

  • 這樣作違背於JVM設置exception處理的原則,JVM會花費更多的時間來處理。
  • 把Code放在try-catch語句中使得一些JVM運行中的優化被封禁。
  • 規範的迭代寫法是通過優化的,經過JVM的內部處理,避免了不少贅餘的檢查機制,是更合適的選擇。

若是咱們在try-catch語句中調用了另外一個數組,這個數組中出現了ArrayIndexOutOfBounds的異常,其中的bug就會被catch exception所矇蔽。相反,標準的迭代寫法會及時的終止線程的執行,報出錯誤而且給出追蹤錯誤的路徑讓程序員更輕鬆地定位bug的來源。多線程

如今咱們經過Java的Iterator接口來看一下標準迭代寫法,在標準的迭代寫法中,咱們利用hasNext()做爲state-testing判斷方法,來實現state-dependent方法next(),代碼以下:app

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

綜上所述,Exception是爲了異常或者說例外的狀況而準備的,不該該在普通的語句中使用,而且程序員也不應寫出強迫他人在正常流程的語句中使用Exception的API。less

Checked與Unchecked的區別

在Java中Throwable是Exception與Error的父類,而在Effective Java書中,Throwable被分爲了如下三類:ide

  1. Checked Exceptions
  2. Runtime Exceptions
  3. Errors

其中,2和3都是Unchecked Throwable,因此在咱們分析Java的異常類時,從Checked與Unchecked兩個邏輯角度來分析會更加清晰。工具

Checked Exception指那些在編譯過程當中會檢查的,這類錯誤在運行中是「可恢復的」。咱們須要在寫程序時將其拋出,換而言之,這些異常應該並非由程序員所致使,而是相似」例行檢查「。

相反,Runtime Exception指的就是程序員自己製造出來的錯誤,在文章的第一部分中咱們已經明確指出,此類錯誤不該該被拋出,而應該由程序員本身去修復。須要注意的是,通常來講,咱們本身設計的Exception應該做爲Runtime Exception的直接或者間接子類。若是你對Exception理解得比較淺,暴力地把Runtime Exception的子類背下來,對debug的幫助也至關大,能夠快速定位代碼中的問題。

Errors與Exception不一樣,他是與JVM相關的,當你在寫算法時看到棧溢出,那並非你對語言的理解致使你的代碼出現漏洞,而是你的數據結構使得JVM出現resource deficiency或invariant failures使得程序沒法繼續執行,因此看到Errors的時候,咱們也不該將其拋出,而是應該對代碼結構進行修改處理。

綜上所述,若是在運行中可恢復,那麼咱們就應該將這種Checked Exception拋出。當不清楚該如何作的時候,拋出Runtime Exception。重要的是,不要定義既不是Checked Exception子類也不是Runtime Exception子類的Throwable,而且記得在你自定義的Checked Exception中加入方法使代碼能在運行中恢復。

Checked Exception的使用技巧

咱們常常會遇到這種問題,在一個方法中,有一行代碼須要拋出Exception,咱們須要將他包裹在try-catch語句中。在Java8以後,咱們在使用此API時必須拋出這個異常,這極大地下降了咱們代碼的質量。

解決這個問題最簡單的方法可能就是咱們在運行此方法是不返回任何值,可是若是這樣作咱們就少了不少經過此方法返回信息和數據的機會。

所以咱們提供了另外一種解決方式,那即是經過將須要拋出Checked Exception的方法拆爲兩個方法,使其轉變爲一個Unchecked Exception。第一個方法經過返回一個boolean值來指明此Exception是否應該被拋出,第二個再進行剩餘的操做。下面是一個轉變的簡單例子。

包裹在try-catch中的語句:

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

下面是改造後的代碼:

if (ted.understand(book)) {
    ted.read(book);
} else {
    //...do sth.
}
複製代碼

簡單來講,就是原本是再Ted」讀「這個方法中拋出他看不懂這個書的異常,但咱們將其拆分爲」是否理解「與「讀」兩個方法對其進行重構,來避免try-catch的運用。

總的來講,重構Checked Exception是爲了代碼更簡潔更可靠,避免了對Checked Exception的過分使用,由於過分使用會致使API對使用者很不友好。在遇到上面所說的狀況時,首先考慮可否使用返回值爲空的方法,由於這是最直接最簡單的解決方式。

優先使用標準庫中的Exception

使用Java庫中提供地Exception有三大好處:

  1. 使你的API更容易地被學習與使用,由於大多數程序員都瞭解標準的異常
  2. 讓使用了你的API的程序閱讀起來更輕鬆
  3. 更少地佔用內存而且更快地對Class進行加載(JVM)

不要直接重用Exception, RuntimeException, Throwable或是Error這些父類,經常使用的Exception在下表中列出。

Exception 使用場景
IllegalArgumentException 不匹配的非空參數的傳遞
IllegalStateException 未初始化的對象(對象狀態不匹配)
NullPointerException 在未預期的狀況下遭遇空指針
IndexOutOfBoundsException 索引參數超出範圍
ConcurrentModificationException 多線程對同一個對象進行修改
UnsupportedOperationException 此對象不支持對此方法的引用

須要注意的是,重用的Exception必定要與記錄的語義一致,在文檔中詳細說明,並不僅是簡單地匹配Exception的名字。

結語

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

關於異常處理對於不少人來講只是Alt+Enter,可是在代碼優化階段常常很讓人頭疼,但願本文能使你們有所啓發,對於接下來教程中的一些代碼有更好的理解,也歡迎你們提問,共同提升。

附錄:Effective Java 讀書筆記

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.

相關文章
相關標籤/搜索