Tips
書中的源代碼地址:https://github.com/jbloch/effective-java-3e-source-code
注意,書中的有些代碼裏方法是基於Java 9 API中的,因此JDK 最好下載 JDK 9以上的版本。java
當充分發揮異常的優點時,它能夠提升程序的可讀性、可靠性和可維護性。若是使用不當,則會產生相反的效果。本章提供了有效使用異常的指南。git
有一天,若是你運氣很差,你可能會偶然發現這樣一段代碼:程序員
// Horrible abuse of exceptions. Don't ever do this! try { int i = 0; while(true) range[i++].climb(); } catch (ArrayIndexOutOfBoundsException e) { }
這段代碼是作什麼的?檢查結果看來一點也不明顯,這就是不使用它的充分理由(條目 67)。事實證實,這是一種用於循環遍歷數組元素的很是錯誤的習慣用法。當試圖訪問數組邊界以外的第一個數組元素時,無限循環經過拋出、捕獲和忽略ArrayIndexOutOfBoundsException
異常來終止。它應該等同於循環數組的標準習慣用法,任何Java程序員均可以一眼就能識別出來:github
for (Mountain m : range) m.climb();
那麼爲何有人會使用基於異常的循環而不是嘗試和正確的用法? 根據錯誤推理提升性能是一種錯誤的嘗試,由於虛擬機檢查全部數組訪問的邊界,由編譯器隱藏但仍然存在於for-each循環中的正常循環終止測試是多餘的,應該避免。 這個推理有三個問題:數組
事實上,基於異常的習慣用法比標準用法慢得多。在個人機器上,100個元素的數組,基於異常的習慣用法的速度大約是標準習慣用法的兩倍。併發
基於異常的循環不只混淆了代碼的目的,下降了代碼的性能,並且不能保證它能正常工做。若是循環中存在bug,使用異常進行流控制能夠掩蓋該bug,從而大大增長調試過程的複雜性。假設循環體中的計算調用一個方法,該方法對一些不相關的數組執行越界訪問。若是使用合理的循環習慣用法,該bug將生成一個未捕獲的異常,致使線程當即終止,並帶有完整的堆棧跟蹤。若是使用錯誤的基於異常的循環,則會捕獲與bug相關的異常,並將其誤解爲正常的循環終止。ide
這個示例說明的道理很簡單:顧名思義,異常僅用於特殊狀況; 它們永遠不該該用於正常的控制流程。 一般來講,使用標準的、易於識別的習慣用法,而不是聲稱能夠提供更好性能的過分聰明的技術。即便性能優點是真實存在的,但在穩步改進平臺實現的狀況下,這種優點也可能不復存在。然而,來自過分聰明的技術的細微缺陷和維護難題確定會繼續存在。性能
這個原則對API設計也有影響。一個設計良好的API不能強迫它的客戶端爲正常的控制流使用異常。只有在某些不可預知的條件下才能調用具備「狀態依賴(state-dependent)」方法的類,一般應該有一個單獨的「狀態測試(state-testing)」方法,指示是否適合調用狀態依賴方法。例如,Iterator接口具備依賴於狀態的next方法和對應的狀態測試方法hasNext。這支持使用傳統for循環(以及for-each循環,其中內部使用了hasNext方法)在集合上迭代的標準習慣用法:測試
for (Iterator<Foo> i = collection.iterator(); i.hasNext(); ) { Foo foo = i.next(); ... }
若是Iterator缺乏hasNext方法,則客戶端將被迫執行此操做:優化
// Do not use this hideous code for iteration over a collection! try { Iterator<Foo> i = collection.iterator(); while(true) { Foo foo = i.next(); ... } } catch (NoSuchElementException e) { }
這數組迭代的例子很是相似於本條目一開始的那個例子。除了冗長和誤導以外,基於異常的循環極可能執行得不好,而且能夠掩蓋系統中不相關部分中的bug。
提供單獨的狀態測試方法的另外一種方式是,讓依賴於狀態的方法返回一個空的Optional值(條目 55),或者在它不能執行所需的計算時返回一個區分值,好比null。
下面是一些指導原則,幫助你在狀態測試方法,Optional的或區分的返回值之間進行選擇。若是要在沒有外部同步的狀況下併發地訪問對象,或者受制於外部引起的狀態轉換,則必須使用Optional的或可區分的返回值,由於在調用狀態測試方法與其依賴於狀態的方法之間的間隔內,對象的狀態可能會發生變化。若是一個單獨的狀態測試方法將重複依賴於狀態的方法的工做,那麼性能問題可能要求使用一個Optional的或可區分的返回值。在全部其餘條件相同的狀況下,狀態測試方法略優於區分的返回值。它提供了更好的可讀性,並且不正確的使用可能更容易檢測:若是忘記調用狀態測試方法,依賴於狀態的方法將拋出異常,使錯誤變得明顯;若是忘記檢查一個可區分的返回值,那麼這個bug可能很微妙。這不是Optional返回值的問題。
總之,異常是針對特殊狀況而設計的。不要將它們用於正常的控制流程,也不要編寫強制其餘人這樣作的API。