## Chapter 10. Exceptions(異常)
### Item 69: Use exceptions only for exceptional conditions(僅在確有異常條件下使用異常)
Someday, if you are unlucky, you may stumble across a piece of code that looks something like this:
你可能會偶然發現這樣一段代碼:
```
// Horrible abuse of exceptions. Don't ever do this!
try {
int i = 0;
while(true)
range[i++].climb();
}
catch (ArrayIndexOutOfBoundsException e) {
}
```
What does this code do? It’s not at all obvious from inspection, and that’s reason enough not to use it (Item 67). It turns out to be a horribly ill-conceived idiom for looping through the elements of an array. The infinite loop terminates by throwing, catching, and ignoring an ArrayIndexOutOfBoundsException when it attempts to access the first array element outside the bounds of the array. It’s supposed to be equivalent to the standard idiom for looping through an array, which is instantly recognizable to any Java programmer:
這段代碼是做什么的?從表面上看,一點也不明顯,這足以成為不使用它的充分理由([Item-67](/Chapter-9/Chapter-9-Item-67-Optimize-judiciously.md))。事實證明,這是一個用于遍歷數組的元素的非常糟糕的習慣用法。當試圖訪問數組邊界之外的數組元素時,通過拋出、捕獲和忽略 ArrayIndexOutOfBoundsException 來終止無限循環。如下循環遍歷數組的標準習慣用法,任何 Java 程序員都可以立即識別它:
```
for (Mountain m : range)
m.climb();
```
So why would anyone use the exception-based loop in preference to the tried and true? It’s a misguided attempt to improve performance based on the faulty reasoning that, since the VM checks the bounds of all array accesses, the normal loop termination test—hidden by the compiler but still present in the for-each loop—is redundant and should be avoided. There are three things wrong with this reasoning:
那么,為什么會有人使用基于異常的循環而不使用習慣的循環模式呢?由于 VM 檢查所有數組訪問的邊界,所以誤認為正常的循環終止測試被編譯器隱藏了,但在 for-each 循環中仍然可見,這無疑是多余的,應該避免,因此利用錯誤判斷機制來提高性能是錯誤的。這種思路有三點誤區:
- Because exceptions are designed for exceptional circumstances, there is little incentive for JVM implementors to make them as fast as explicit tests.
因為異常是為特殊情況設計的,所以 JVM 實現幾乎不會讓它們像顯式測試一樣快。
- Placing code inside a try-catch block inhibits certain optimizations that JVM implementations might otherwise perform.
將代碼放在 try-catch 塊中會抑制 JVM 可能執行的某些優化。
- The standard idiom for looping through an array doesn’t necessarily result in redundant checks. Many JVM implementations optimize them away.
遍歷數組的標準習慣用法不一定會導致冗余檢查。許多 JVM 實現對它們進行了優化。
In fact, the exception-based idiom is far slower than the standard one. On my machine, the exception-based idiom is about twice as slow as the standard one for arrays of one hundred elements.
事實上,基于異常的用法比標準用法慢得多。在我的機器上,用 100 個元素的數組測試,基于異常的用法與標準用法相比速度大約慢了兩倍。
Not only does the exception-based loop obfuscate the purpose of the code and reduce its performance, but it’s not guaranteed to work. If there is a bug in the loop, the use of exceptions for flow control can mask the bug, greatly complicating the debugging process. Suppose the computation in the body of the loop invokes a method that performs an out-of-bounds access to some unrelated array. If a reasonable loop idiom were used, the bug would generate an uncaught exception, resulting in immediate thread termination with a full stack trace. If the misguided exception-based loop were used, the bug-related exception would be caught and misinterpreted as a normal loop termination.
基于異常的循環不僅混淆了代碼的目的,降低了代碼的性能,而且不能保證它能正常工作。如果循環中存在 bug,使用異常進行流程控制會掩蓋該 bug,從而大大增加調試過程的復雜性。假設循環體中的計算步驟調用一個方法,該方法對一些不相關的數組執行越界訪問。如果使用合理的循環習慣用法,該 bug 將生成一個未捕獲的異常,導致線程立即終止,并帶有完整的堆棧跟蹤。相反,如果使用了基于異常的循環,當捕獲與 bug 相關的異常時,會將其誤判為正常的循環終止條件。
The moral of this story is simple: **Exceptions are, as their name implies, to be used only for exceptional conditions; they should never be used for ordinary control flow.** More generally, use standard, easily recognizable idioms in preference to overly clever techniques that purport to offer better performance. Even if the performance advantage is real, it may not remain in the face of steadily improving platform implementations. The subtle bugs and maintenance headaches that come from overly clever techniques, however, are sure to remain.
這個案例的寓意很簡單:**顧名思義,異常只適用于確有異常的情況;它們不應該用于一般的控制流程。** 更進一步說,使用標準的、易于識別的習慣用法,而不是聲稱能夠提供更好性能的過于抖機靈的技術。即使性能優勢是真實存在的,但在穩步改進平臺實現的前提下,這種優勢也并不可靠。而且,來自抖機靈的技術存在的細微缺陷和維護問題肯定會繼續存在。
This principle also has implications for API design. **A well-designed API must not force its clients to use exceptions for ordinary control flow.** A class with a “state-dependent” method that can be invoked only under certain unpredictable conditions should generally have a separate “state-testing” method indicating whether it is appropriate to invoke the state-dependent method. For example, the Iterator interface has the state-dependent method next and the corresponding state-testing method hasNext. This enables the standard idiom for iterating over a collection with a traditional for loop (as well as the for-each loop, where the hasNext method is used internally):
這個原則對 API 設計也有影響。一個設計良好的 API 不能迫使其客戶端為一般的控制流程使用異常。只有在某些不可預知的條件下才能調用具有「狀態依賴」方法的類,通常應該有一個單獨的「狀態測試」方法,表明是否適合調用「狀態依賴」方法。例如,Iterator 接口具有「狀態依賴」的 next 方法和對應的「狀態測試」方法 hasNext。這使得傳統 for 循環(在 for-each 循環內部也使用了 hasNext 方法)在集合上進行迭代成為標準習慣用法:
```
for (Iterator<Foo> i = collection.iterator(); i.hasNext(); ) {
Foo foo = i.next();
...
}
```
If Iterator lacked the hasNext method, clients would be forced to do this instead:
如果 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) {
}
```
This should look very familiar after the array iteration example that began this item. In addition to being wordy and misleading, the exception-based loop is likely to perform poorly and can mask bugs in unrelated parts of the system.
這與一開始舉例的對數組進行迭代的例子非常相似,除了冗長和誤導之外,基于異常的循環執行效果可能很差,并且會掩蓋系統中不相關部分的 bug。
An alternative to providing a separate state-testing method is to have the statedependent method return an empty optional (Item 55) or a distinguished value such as null if it cannot perform the desired computation.
提供單獨的「狀態測試」方法的另一種方式,就是讓「狀態依賴」方法返回一個空的 Optional 對象([Item-55](/Chapter-8/Chapter-8-Item-55-Return-optionals-judiciously.md)),或者在它不能執行所需的計算時返回一個可識別的值,比如 null。
Here are some guidelines to help you choose between a state-testing method and an optional or distinguished return value. If an object is to be accessed concurrently without external synchronization or is subject to externally induced state transitions, you must use an optional or distinguished return value, as the object’s state could change in the interval between the invocation of a state-testing method and its state-dependent method. Performance concerns may dictate that an optional or distinguished return value be used if a separate statetesting method would duplicate the work of the state-dependent method. All other things being equal, a state-testing method is mildly preferable to a distinguished return value. It offers slightly better readability, and incorrect use may be easier to detect: if you forget to call a state-testing method, the statedependent method will throw an exception, making the bug obvious; if you forget to check for a distinguished return value, the bug may be subtle. This is not an issue for optional return values.
有一些指導原則,幫助你在「狀態測試」方法、Optional、可識別的返回值之間進行選擇。(1)如果要在沒有外部同步的情況下并發地訪問對象,或者受制于外部條件的狀態轉換,則必須使用 Optional 或可識別的返回值,因為對象的狀態可能在調用「狀態測試」方法與「狀態依賴」方法的間隔中發生變化。(2)如果一個單獨的「狀態測試」方法重復「狀態依賴」方法的工作,從性能問題考慮,可能要求使用 Optional 或可識別的返回值。(3)在所有其他條件相同的情況下,「狀態測試」方法略優于可識別的返回值。它提供了較好的可讀性,而且不正確的使用可能更容易被檢測:如果你忘記調用「狀態測試」方法,「狀態依賴」方法將拋出異常,使錯誤顯而易見;(4)如果你忘記檢查一個可識別的返回值,那么這個 bug 可能很難發現。但是這對于返回 Optional 對象的方式來說不是問題。
In summary, exceptions are designed for exceptional conditions. Don’t use them for ordinary control flow, and don’t write APIs that force others to do so.
總之,異常是為確有異常的情況設計的。不要將它們用于一般的控制流程,也不要編寫強制其他人這樣做的 API。
---
**[Back to contents of the chapter(返回章節目錄)](/Chapter-10/Chapter-10-Introduction.md)**
- **Next Item(下一條目):[Item 70: Use checked exceptions for recoverable conditions and runtime exceptions for programming errors(對可恢復情況使用 checked 異常,對編程錯誤使用運行時異常)](/Chapter-10/Chapter-10-Item-70-Use-checked-exceptions-for-recoverable-conditions-and-runtime-exceptions-for-programming-errors.md)**
- Chapter 2. Creating and Destroying Objects(創建和銷毀對象)
- Item 1: Consider static factory methods instead of constructors(考慮以靜態工廠方法代替構造函數)
- Item 2: Consider a builder when faced with many constructor parameters(在面對多個構造函數參數時,請考慮構建器)
- Item 3: Enforce the singleton property with a private constructor or an enum type(使用私有構造函數或枚舉類型實施單例屬性)
- Item 4: Enforce noninstantiability with a private constructor(用私有構造函數實施不可實例化)
- Item 5: Prefer dependency injection to hardwiring resources(依賴注入優于硬連接資源)
- Item 6: Avoid creating unnecessary objects(避免創建不必要的對象)
- Item 7: Eliminate obsolete object references(排除過時的對象引用)
- Item 8: Avoid finalizers and cleaners(避免使用終結器和清除器)
- Item 9: Prefer try with resources to try finally(使用 try-with-resources 優于 try-finally)
- Chapter 3. Methods Common to All Objects(對象的通用方法)
- Item 10: Obey the general contract when overriding equals(覆蓋 equals 方法時應遵守的約定)
- Item 11: Always override hashCode when you override equals(當覆蓋 equals 方法時,總要覆蓋 hashCode 方法)
- Item 12: Always override toString(始終覆蓋 toString 方法)
- Item 13: Override clone judiciously(明智地覆蓋 clone 方法)
- Item 14: Consider implementing Comparable(考慮實現 Comparable 接口)
- Chapter 4. Classes and Interfaces(類和接口)
- Item 15: Minimize the accessibility of classes and members(盡量減少類和成員的可訪問性)
- Item 16: In public classes use accessor methods not public fields(在公共類中,使用訪問器方法,而不是公共字段)
- Item 17: Minimize mutability(減少可變性)
- Item 18: Favor composition over inheritance(優先選擇復合而不是繼承)
- Item 19: Design and document for inheritance or else prohibit it(繼承要設計良好并且具有文檔,否則禁止使用)
- Item 20: Prefer interfaces to abstract classes(接口優于抽象類)
- Item 21: Design interfaces for posterity(為后代設計接口)
- Item 22: Use interfaces only to define types(接口只用于定義類型)
- Item 23: Prefer class hierarchies to tagged classes(類層次結構優于帶標簽的類)
- Item 24: Favor static member classes over nonstatic(靜態成員類優于非靜態成員類)
- Item 25: Limit source files to a single top level class(源文件僅限有單個頂層類)
- Chapter 5. Generics(泛型)
- Item 26: Do not use raw types(不要使用原始類型)
- Item 27: Eliminate unchecked warnings(消除 unchecked 警告)
- Item 28: Prefer lists to arrays(list 優于數組)
- Item 29: Favor generic types(優先使用泛型)
- Item 30: Favor generic methods(優先使用泛型方法)
- Item 31: Use bounded wildcards to increase API flexibility(使用有界通配符增加 API 的靈活性)
- Item 32: Combine generics and varargs judiciously(明智地合用泛型和可變參數)
- Item 33: Consider typesafe heterogeneous containers(考慮類型安全的異構容器)
- Chapter 6. Enums and Annotations(枚舉和注解)
- Item 34: Use enums instead of int constants(用枚舉類型代替 int 常量)
- Item 35: Use instance fields instead of ordinals(使用實例字段替代序數)
- Item 36: Use EnumSet instead of bit fields(用 EnumSet 替代位字段)
- Item 37: Use EnumMap instead of ordinal indexing(使用 EnumMap 替換序數索引)
- Item 38: Emulate extensible enums with interfaces(使用接口模擬可擴展枚舉)
- Item 39: Prefer annotations to naming patterns(注解優于命名模式)
- Item 40: Consistently use the Override annotation(堅持使用 @Override 注解)
- Item 41: Use marker interfaces to define types(使用標記接口定義類型)
- Chapter 7. Lambdas and Streams(λ 表達式和流)
- Item 42: Prefer lambdas to anonymous classes(λ 表達式優于匿名類)
- Item 43: Prefer method references to lambdas(方法引用優于 λ 表達式)
- Item 44: Favor the use of standard functional interfaces(優先使用標準函數式接口)
- Item 45: Use streams judiciously(明智地使用流)
- Item 46: Prefer side effect free functions in streams(在流中使用無副作用的函數)
- Item 47: Prefer Collection to Stream as a return type(優先選擇 Collection 而不是流作為返回類型)
- Item 48: Use caution when making streams parallel(謹慎使用并行流)
- Chapter 8. Methods(方法)
- Item 49: Check parameters for validity(檢查參數的有效性)
- Item 50: Make defensive copies when needed(在需要時制作防御性副本)
- Item 51: Design method signatures carefully(仔細設計方法簽名)
- Item 52: Use overloading judiciously(明智地使用重載)
- Item 53: Use varargs judiciously(明智地使用可變參數)
- Item 54: Return empty collections or arrays, not nulls(返回空集合或數組,而不是 null)
- Item 55: Return optionals judiciously(明智地的返回 Optional)
- Item 56: Write doc comments for all exposed API elements(為所有公開的 API 元素編寫文檔注釋)
- Chapter 9. General Programming(通用程序設計)
- Item 57: Minimize the scope of local variables(將局部變量的作用域最小化)
- Item 58: Prefer for-each loops to traditional for loops(for-each 循環優于傳統的 for 循環)
- Item 59: Know and use the libraries(了解并使用庫)
- Item 60: Avoid float and double if exact answers are required(若需要精確答案就應避免使用 float 和 double 類型)
- Item 61: Prefer primitive types to boxed primitives(基本數據類型優于包裝類)
- Item 62: Avoid strings where other types are more appropriate(其他類型更合適時應避免使用字符串)
- Item 63: Beware the performance of string concatenation(當心字符串連接引起的性能問題)
- Item 64: Refer to objects by their interfaces(通過接口引用對象)
- Item 65: Prefer interfaces to reflection(接口優于反射)
- Item 66: Use native methods judiciously(明智地使用本地方法)
- Item 67: Optimize judiciously(明智地進行優化)
- Item 68: Adhere to generally accepted naming conventions(遵守被廣泛認可的命名約定)
- Chapter 10. Exceptions(異常)
- Item 69: Use exceptions only for exceptional conditions(僅在確有異常條件下使用異常)
- Item 70: Use checked exceptions for recoverable conditions and runtime exceptions for programming errors(對可恢復情況使用 checked 異常,對編程錯誤使用運行時異常)
- Item 71: Avoid unnecessary use of checked exceptions(避免不必要地使用 checked 異常)
- Item 72: Favor the use of standard exceptions(鼓勵復用標準異常)
- Item 73: Throw exceptions appropriate to the abstraction(拋出能用抽象解釋的異常)
- Item 74: Document all exceptions thrown by each method(為每個方法記錄會拋出的所有異常)
- Item 75: Include failure capture information in detail messages(異常詳細消息中應包含捕獲失敗的信息)
- Item 76: Strive for failure atomicity(盡力保證故障原子性)
- Item 77: Don’t ignore exceptions(不要忽略異常)
- Chapter 11. Concurrency(并發)
- Item 78: Synchronize access to shared mutable data(對共享可變數據的同步訪問)
- Item 79: Avoid excessive synchronization(避免過度同步)
- Item 80: Prefer executors, tasks, and streams to threads(Executor、task、流優于直接使用線程)
- Item 81: Prefer concurrency utilities to wait and notify(并發實用工具優于 wait 和 notify)
- Item 82: Document thread safety(文檔應包含線程安全屬性)
- Item 83: Use lazy initialization judiciously(明智地使用延遲初始化)
- Item 84: Don’t depend on the thread scheduler(不要依賴線程調度器)
- Chapter 12. Serialization(序列化)
- Item 85: Prefer alternatives to Java serialization(優先選擇 Java 序列化的替代方案)
- Item 86: Implement Serializable with great caution(非常謹慎地實現 Serializable)
- Item 87: Consider using a custom serialized form(考慮使用自定義序列化形式)
- Item 88: Write readObject methods defensively(防御性地編寫 readObject 方法)
- Item 89: For instance control, prefer enum types to readResolve(對于實例控制,枚舉類型優于 readResolve)
- Item 90: Consider serialization proxies instead of serialized instances(考慮以序列化代理代替序列化實例)