Tips
書中的源代碼地址:https://github.com/jbloch/effective-java-3e-source-code
注意,書中的有些代碼裏方法是基於Java 9 API中的,因此JDK 最好下載 JDK 9以上的版本。java
在對象拋出異常以後,一般但願對象仍然處於定義良好的可用狀態,即便失敗發生在執行操做中。對於檢查異常尤爲如此,調用者但願從檢查異常中恢復。通常來講,失敗的方法調用應該使對象處於調用以前的狀態。具備此屬性的方法稱爲失敗原子性( failure-atomic)。git
有幾種方法能夠達到這種效果。最簡單的方法是設計不可變對象(條目 17)。若是對象是不可變的,則失敗原子性是必然的。若是一個操做失敗,它可能會阻止建立一個新對象,可是它不會讓一個現有對象處於不一致的狀態,由於每一個對象的狀態在建立時是一致的,而且在建立後不能修改。github
對於對可變對象進行操做的方法,實現失敗原子性的最經常使用方法是:在執行操做以前檢查參數的有效性(條目 49)。 這致使在對象修改開始以前就會拋出大多數異常。 例如,考慮條目 7中的Stack.pop
方法:數組
public Object pop() { if (size == 0) throw new EmptyStackException(); Object result = elements[--size]; elements[size] = null; // Eliminate obsolete reference return result; }
若是取消了初始大小檢查,當該方法試圖從空棧中彈出元素時,仍然會拋出異常。可是,這會使size屬性處於不一致的(負數)狀態,致使之後對對象的任何方法調用失敗。此外,pop方法拋出的ArrayIndexOutOfBoundsException針對抽象來說是不合適的。(條目 73)。數據結構
實現失敗原子性的一種密切相關的方法是對計算進行排序,以便任何可能失敗的部分在修改對象的部分以前發生。 在執行部分計算時進行參數檢查,此方法是前一個方法的天然擴展。 例如,考慮TreeMap的狀況,其元素按照某種順序排序。 爲了向TreeMap添加元素,元素必須是可使用TreeMap的順序進行比較的類型。 在以任何方式修改tree以前,嘗試添加錯誤鍵的元素天然會由於在tree中搜索元素失敗而致使ClassCastException異常。併發
實現失敗原子性的第三種方法是,在對象的臨時拷貝上執行操做,並在操做完成後用臨時拷貝替換對象的內容。當數據存儲在臨時數據結構中後,計算能夠更快地執行時,這種方法天然會出現。例如,一些排序方法在排序以前將其輸入列表拷貝到數組中,以下降訪問排序內循環中的元素的成本。這樣作是爲了提升性能,可是做爲一個額外的好處,它確保若是排序失敗,輸入列表保持不變。性能
實現失敗原子性的最後的方法是,編寫恢復代碼(recovery code),但這種作法並不長用,該代碼攔截在操做中發生的失敗,並使對象將其狀態回滾到操做開始以前的點。 此方法主要用於持久性的(基於磁盤)的數據結構。atom
雖然失敗原子性一般是可取的,但它並不老是能夠實現的。例如,若是兩個線程試圖在沒有適當同步的狀況下併發地修改同一個對象,那麼該對象可能會處於不一致的狀態。所以,若是假定在捕捉到ConcurrentModificationException以後對象仍然可用,那就錯了。錯誤是不可恢復的,因此方法在拋出AssertionError時,甚至不須要嘗試保存失敗原子性。線程
即便在可能存在實現失敗原子性的狀況下,也並不是老是可取的。 對於某些操做,它會顯着增長成本或複雜性。 也就是說,一旦你意識到這個問題,一般均可以自由而輕鬆地作到失敗原子性。設計
總之,做爲規則,任何生成的異常都是方法規範的一部分,應該使對象處於方法調用以前的狀態。 違反此規則的地方,API文檔應清楚地指出該對象將保留在哪一種狀態。遺憾的是,許多現有的API文檔沒法實現這一理想。