第八條: 避免使用 Finalizer(finally) 和 Cleaner(try-with-resources)機制
- Java 9 開始,F(xiàn)inalizer 機制已被棄用,但仍被 Java 類庫所使用。 Java 9 中 Cleaner 機制代替了 Finalizer 機制。Cleaner 機制不如 Finalizer 機制那樣危險,但仍然是不可預測,運行緩慢并且通常是不必要的。
- Finalizer 和 Cleaner 機制的一個缺點是不能保證他們能夠及時執(zhí)行。在一個對象變得無法訪問時,到Finalizer 和 Cleaner 機制開始運行時,這期間的時間是任意長的。 這意味著你永遠不應該 Finalizer 和 Cleaner 機制做任何時間敏感(time-critical)的事情。
- 不要相信 System.gc 和 System.runFinalization 方法。 他們可能會增加 Finalizer 和 Cleaner 機制被執(zhí)行的幾率,但不能保證一定會執(zhí)行。 曾經(jīng)聲稱做出這種保證的兩個方法System.runFinalizersOnExit 和它的孿生兄弟 Runtime.runFinalizersOnExit ,包含致命的缺陷,并已被棄用了。
- Finalizer 機制的另一個問題是在執(zhí)行 Finalizer 機制過程中,未捕獲的異常會被忽略,并且該對象的 Finalizer 機制也會終止 。未捕獲的異常會使其他對象陷入一種損壞的狀態(tài)(corrupt state)。如果另一個線程試圖使用這樣一個損壞的對象,可能會導致任意不確定的行為。通常情況下,未捕獲的異常將終止線程并打印堆棧跟蹤(
stacktrace),但如果發(fā)生在 Finalizer 機制中,則不會發(fā)出警告。Cleaner 機制沒有這個問題,因為使用 Cleaner 機制的類庫可以控制其線程。 - 使用Cleaner時Finalizer的效率的差不多50倍。
- finalizer 機制有一個嚴重的安全問題:如果一個異常是從構造方法或它的序列化中拋出的—— readObject 和 readResolve 方法 惡意子類的 finalizer 機制可以運行在本應該的部分構造對象上。finalizer 機制可以在靜態(tài)字屬性記錄對對象的引用,防止其被垃圾收集。
解決方案
類實現(xiàn) AutoCloseable接口,并用try-with-resources確保終止。
實例代碼如下:
import java.lang.ref.Cleaner;
// An autocloseable class using a cleaner as a safety net (Page 32)
public class Room implements AutoCloseable {
private static final Cleaner cleaner = Cleaner.create();
// Resource that requires cleaning. Must not refer to Room!
private static class State implements Runnable {
int numJunkPiles; // Number of junk piles in this room
State(int numJunkPiles) {
this.numJunkPiles = numJunkPiles;
}
// Invoked by close method or cleaner
@Override public void run() {
System.out.println("Cleaning room");
System.out.println(Thread.currentThread().getName());
numJunkPiles = 0;
}
}
// The state of this room, shared with our cleanable
private final State state;
// Our cleanable. Cleans the room when it’s eligible for gc
private final Cleaner.Cleanable cleanable;
public Room(int numJunkPiles) {
state = new State(numJunkPiles);
cleanable = cleaner.register(this, state);
}
@Override public void close() {
cleanable.clean();
}
}
調試代碼如下:
// Well-behaved client of resource with cleaner safety-net (Page 33)
public class Adult {
public static void main(String[] args) {
try (Room myRoom = new Room(7)) {
System.out.println("Goodbye");
System.out.println(Thread.currentThread().getName());
}
}
}
運行結果如下:
Goodbye
main
Cleaning room
main
第九條: 使用 try-with-resources 語句替代 try-finally 語句
- 精簡,不易出錯,可讀性好。
- try-with-resources不會覆蓋異常。例如在讀取文件時,readLine方法發(fā)生異常,調用 close 方法可能會異常。try-finally第二個異常會覆蓋第一個異常。try-with-resources 如果調用 readLine 和(不可見) close 方法都拋出異常,則后一個異常將被抑制,而不是前者。
總結
結論很明確:在處理必須關閉的資源時,使用 try-with-resources 語句替代 try-finally 語句。 生成的代碼更簡潔,更清晰,并且生成的異常更有用。 try-with-resources 語句在編寫必須關閉資源的代碼時會更容易,也不會出錯,而使用 try-finally 語句實際上是不可能的。
第十條:重寫 equals 方法時遵守通用約定
- 什么時候需要重寫 equals 方法呢?
如果一個類包含一個邏輯相等的概念,此概念有別于對象標識,而且父類還沒有重寫過 equals 方法。 - 當你重寫 equals 方法時,必須遵守它的通用約定。
- 自反性: 對于任何非空引用 x, x.equals(x) 必須返回 true。
- 對稱性: 對于任何非空引用 x 和 y,如果且僅當 y.equals(x) 返回 true 時 x.equals(y) 必須返回 true。
- 傳遞性: 對于任何非空引用 x、y、z,如果 x.equals(y) 返回 true, y.equals(z) 返回 true,則
x.equals(z) 必須返回 true。 - 一致性: 對于任何非空引用 x 和 y,如果在 equals 比較中使用的信息沒有修改,則 x.equals(y) 的多次調用
必須始終返回 true 或始終返回 false。 - 對于任何非空引用 x, x.equals(null) 必須返回 false。
違反以上約定,會導致程序各種錯誤。
第十一點:重寫 equals 方法時同時也要重寫 hashcode 方法
- 在每個類中,在重寫 equals 方法的時侯,一定要重寫 hashcode 方法。 如果不這樣做,你的類違反了 hashCode的通用約定,這會阻止它在 HashMap 和 HashSet 這樣的集合中正常工作。根據(jù) Object 規(guī)范,以下時具體約定。
- 當無法重寫 hashCode 時,所違反第二個關鍵條款是:相等的對象必須具有相等的哈希碼。
- 重寫hashcode方法
- 如果這個屬性是基本類型的,使用 Type.hashCode(f) 方法計算,其中 Type 類是對應屬性 f 基本類型的包裝類。
- 如果該屬性是一個對象引用,并且該類的 equals 方法通過遞歸調用 equals 來比較該屬性,并遞歸地調用hashCode 方法。 如果需要更復雜的比較,則計算此字段的“范式(“canonical representation)”,并在范式上調用 hashCode。 如果該字段的值為空,則使用 0(也可以使用其他常數(shù),但通常來使用 0 表示)。
- 如果屬性 f 是一個數(shù)組,使用Arrays.hashCode方法。
- 如果真的需要哈希函數(shù)而不太可能產(chǎn)生碰撞,請參閱 Guava
框架的的 com.google.common.hash.Hashing [Guava] 方法。 - Objects 類有一個靜態(tài)方法,它接受任意數(shù)量的對象并為它們返回一個哈希碼。
第十二條:始終重寫 toString 方法
- 利于日志打印,日志排查錯誤。
第十三條:謹慎地重寫 clone 方法
- 淺克隆,謹慎使用
- 深克隆
-
實現(xiàn)Cloneable 接口
image.png
第十三條:考慮實現(xiàn) Comparable 接口
通過實現(xiàn) Comparable 接口,可以讓你的類與所有依賴此接口的通用算法和集合實現(xiàn)進行互操作。強烈 建議滿足一下特性。
- 自反性
- 對稱性
- 傳遞性
總而言之,無論何時實現(xiàn)具有合理排序的值類,你都應該讓該類實現(xiàn) Comparable 接口,以便在基于比較的集合中輕松對其實例進行排序,搜索和使用。 比較 compareTo 方法的實現(xiàn)中的字段值時,請避免使用「<」和「>」運算符。 相反,使用包裝類中的靜態(tài) compare 方法或 Comparator 接口中的構建方法。
