寫在最前
最近筆者在撰寫JavaWeb與自動化相結(jié)合的教程蔫缸,上篇入口在這里腿准,第二篇還在創(chuàng)作中,在發(fā)布之前拾碌,讓我們先來討論一個Java的重要技能吐葱,Exception街望。
實現(xiàn)程序的運行是所有初級的程序員所追求的,Thinking in Java 因此成為了很適合入門的一本書弟跑,然而隨著代碼行數(shù)的累積它匕,越來越多的坑也隨之到來。此時窖认,對基礎(chǔ)知識更深層次的理解就尤為關(guān)鍵豫柬。在JavaWeb與自動化結(jié)合的應用中,無腦拋出異常會導致代碼的冗余與羸弱扑浸,今天發(fā)的這篇文章將仔細地對Exception的運用進行分析烧给。
需要注意的是,本篇文章并不是對如何拋出異常的基礎(chǔ)進行講解喝噪,需要讀者對Exception機制有一定了解础嫡,文中部分用例來自Effective Java非竿,在這里同時向讀者推薦這本書作為Java進階的重要工具震庭,文末附錄中有筆者Exception部分的英文筆記供大家參考祈远。
使用Exception的情景
不要在類似迭代的循環(huán)中使用Exception捏境,尤其是涉及ArrayIndexOutOfBounds,如下所示:
try {
int i = 0;
while(true)
array[i++].doSomething();
} catch(ArrayIndexOUtOfBoundsException e) {
}
主要因為此時使用try-catch有三點顯而易見的壞處:
- 這樣做違背于JVM設(shè)置exception處理的原則贞言,JVM會花費更多的時間來處理妄田。
- 把Code放在try-catch語句中使得一些JVM運行中的優(yōu)化被封禁酱塔。
- 規(guī)范的迭代寫法是經(jīng)過優(yōu)化的哩陕,通過JVM的內(nèi)部處理平项,避免了很多贅余的檢查機制,是更合適的選擇悍及。
如果我們在try-catch語句中調(diào)用了另一個數(shù)組闽瓢,這個數(shù)組中出現(xiàn)了ArrayIndexOutOfBounds的異常,其中的bug就會被catch exception所蒙蔽心赶。相反扣讼,標準的迭代寫法會及時的終止線程的執(zhí)行,報出錯誤并且給出追蹤錯誤的路徑讓程序員更輕松地定位bug的來源缨叫。
現(xiàn)在我們通過Java的Iterator接口來看一下標準迭代寫法椭符,在標準的迭代寫法中,我們利用hasNext()作為state-testing判斷方法弯汰,來實現(xiàn)state-dependent方法next()艰山,代碼如下:
for (Iterator<Foo> i = collection.iterator(); i.hasNext(); ) {
Foo foo = i.next();
//...
}
綜上所述,Exception是為了異秤缴粒或者說例外的情況而準備的,不應該在普通的語句中使用摔吏,并且程序員也不該寫出強迫他人在正常流程的語句中使用Exception的API鸽嫂。
Checked與Unchecked的區(qū)別
在Java中Throwable是Exception與Error的父類纵装,而在Effective Java書中,Throwable被分為了以下三類:
- Checked Exceptions
- Runtime Exceptions
- Errors
其中据某,2和3都是Unchecked Throwable橡娄,所以在我們分析Java的異常類時,從Checked與Unchecked兩個邏輯角度來分析會更加清晰癣籽。
Checked Exception指那些在編譯過程中會檢查的挽唉,這類錯誤在運行中是“可恢復的”。我們需要在寫程序時將其拋出筷狼,換而言之瓶籽,這些異常應該并不是由程序員所導致,而是類似”例行檢查“埂材。
相反塑顺,Runtime Exception指的就是程序員本身制造出來的錯誤,在文章的第一部分中我們已經(jīng)明確指出俏险,此類錯誤不應該被拋出严拒,而應該由程序員自己去修復。需要注意的是竖独,一般來說裤唠,我們自己設(shè)計的Exception應該作為Runtime Exception的直接或者間接子類。如果你對Exception理解得比較淺莹痢,暴力地把Runtime Exception的子類背下來巧骚,對debug的幫助也相當大,可以快速定位代碼中的問題格二。
Errors與Exception不同劈彪,他是與JVM相關(guān)的,當你在寫算法時看到棧溢出顶猜,那并不是你對語言的理解導致你的代碼出現(xiàn)漏洞沧奴,而是你的數(shù)據(jù)結(jié)構(gòu)使得JVM出現(xiàn)resource deficiency或invariant failures使得程序無法繼續(xù)執(zhí)行,所以看到Errors的時候长窄,我們也不應將其拋出滔吠,而是應該對代碼結(jié)構(gòu)進行修改處理。
綜上所述挠日,如果在運行中可恢復疮绷,那么我們就應該將這種Checked Exception拋出。當不清楚該如何做的時候嚣潜,拋出Runtime Exception冬骚。重要的是,不要定義既不是Checked Exception子類也不是Runtime Exception子類的Throwable,并且記得在你自定義的Checked Exception中加入方法使代碼能在運行中恢復只冻。
Checked Exception的使用技巧
我們經(jīng)常會遇到這種問題庇麦,在一個方法中,有一行代碼需要拋出Exception喜德,我們需要將他包裹在try-catch語句中山橄。在Java8之后,我們在使用此API時必須拋出這個異常舍悯,這極大地降低了我們代碼的質(zhì)量航棱。
解決這個問題最簡單的方法可能就是我們在運行此方法是不返回任何值,但是如果這樣做我們就少了很多通過此方法返回信息和數(shù)據(jù)的機會萌衬。
因此我們提供了另一種解決方式饮醇,那便是通過將需要拋出Checked Exception的方法拆為兩個方法,使其轉(zhuǎn)變?yōu)橐粋€Unchecked Exception奄薇。第一個方法通過返回一個boolean值來指明此Exception是否應該被拋出驳阎,第二個再進行剩余的操作。下面是一個轉(zhuǎn)變的簡單例子馁蒂。
包裹在try-catch中的語句:
try {
ted.read(book);
} catch (CheckedException e) {
//...do sth.
}
下面是改造后的代碼:
if (ted.understand(book)) {
ted.read(book);
} else {
//...do sth.
}
簡單來說呵晚,就是本來是再Ted”讀“這個方法中拋出他看不懂這個書的異常,但我們將其拆分為”是否理解“與“讀”兩個方法對其進行重構(gòu)沫屡,來避免try-catch的運用饵隙。
總的來說,重構(gòu)Checked Exception是為了代碼更簡潔更可靠沮脖,避免了對Checked Exception的過度使用金矛,因為過度使用會導致API對使用者很不友好。在遇到上面所說的情況時勺届,首先考慮能否使用返回值為空的方法驶俊,因為這是最直接最簡單的解決方式。
優(yōu)先使用標準庫中的Exception
使用Java庫中提供地Exception有三大好處:
- 使你的API更容易地被學習與使用免姿,因為大多數(shù)程序員都了解標準的異常
- 讓使用了你的API的程序閱讀起來更輕松
- 更少地占用內(nèi)存并且更快地對Class進行加載(JVM)
不要直接重用Exception, RuntimeException, Throwable或是Error這些父類饼酿,常用的Exception在下表中列出。
Exception | 使用場景 |
---|---|
IllegalArgumentException | 不匹配的非空參數(shù)的傳遞 |
IllegalStateException | 未初始化的對象(對象狀態(tài)不匹配) |
NullPointerException | 在未預期的情況下遭遇空指針 |
IndexOutOfBoundsException | 索引參數(shù)超出范圍 |
ConcurrentModificationException | 多線程對同一個對象進行修改 |
UnsupportedOperationException | 此對象不支持對此方法的引用 |
需要注意的是胚膊,重用的Exception一定要與記錄的語義一致故俐,在文檔中詳細說明,并不只是簡單地匹配Exception的名字紊婉。
結(jié)語
除了上面詳述的幾點外药版,還要注意的是,首先喻犁,每個方法拋出的異常都要有文檔槽片。其次何缓,保持異常的原子性。最重要的是筐乳,千萬不要在catch中忽略掉捕獲到的異常歌殃。
關(guān)于異常處理對于很多人來說只是Alt+Enter乔妈,但是在代碼優(yōu)化階段經(jīng)常很讓人頭疼蝙云,希望本文能使大家有所啟發(fā),對于接下來教程中的一些代碼有更好的理解路召,也歡迎大家提問勃刨,共同提高。
附錄: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.