## Chapter 12. Serialization(序列化)
### Item 86: Implement Serializable with great caution(非常謹慎地實現 Serializable)
Allowing a class’s instances to be serialized can be as simple as adding the words implements Serializable to its declaration. Because this is so easy to do, there was a common misconception that serialization requires little effort on the part of the programmer. The truth is far more complex. While the immediate cost to make a class serializable can be negligible, the long-term costs are often substantial.
使類的實例可序列化非常簡單,只需實現 Serializable 接口即可。因為這很容易做到,所以有一個普遍的誤解,認為序列化只需要程序員付出很少的努力。而事實上要復雜得多。雖然使類可序列化的即時代價可以忽略不計,但長期代價通常是巨大的。
**A major cost of implementing Serializable is that it decreases the flexibility to change a class’s implementation once it has been released.** When a class implements Serializable, its byte-stream encoding (or serialized form) becomes part of its exported API. Once you distribute a class widely, you are generally required to support the serialized form forever, just as you are required to support all other parts of the exported API. If you do not make the effort to design a custom serialized form but merely accept the default, the serialized form will forever be tied to the class’s original internal representation. In other words, if you accept the default serialized form, the class’s private and package-private instance fields become part of its exported API, and the practice of minimizing access to fields (Item 15) loses its effectiveness as a tool for information hiding.
**實現 Serializable 接口的一個主要代價是,一旦類的實現被發布,它就會降低更改該類實現的靈活性。** 當類實現 Serializable 時,其字節流編碼(或序列化形式)成為其導出 API 的一部分。一旦廣泛分發了一個類,通常就需要永遠支持序列化的形式,就像需要支持導出 API 的所有其他部分一樣。如果你不努力設計自定義序列化形式,而只是接受默認形式,則序列化形式將永遠綁定在類的原始內部實現上。換句話說,如果你接受默認的序列化形式,類中私有的包以及私有實例字段將成為其導出 API 的一部分,此時最小化字段作用域([Item-15](/Chapter-4/Chapter-4-Item-15-Minimize-the-accessibility-of-classes-and-members.md))作為信息隱藏的工具,將失去其有效性。
If you accept the default serialized form and later change a class’s internal representation, an incompatible change in the serialized form will result. Clients attempting to serialize an instance using an old version of the class and deserialize it using the new one (or vice versa) will experience program failures. It is possible to change the internal representation while maintaining the original serialized form (using ObjectOutputStream.putFields and ObjectInputStream.readFields), but it can be difficult and leaves visible warts in the source code. If you opt to make a class serializable, you should carefully design a high-quality serialized form that you’re willing to live with for the long haul (Items 87, 90). Doing so will add to the initial cost of development, but it’s worth the effort. Even a well-designed serialized form places constraints on the evolution of a class; an ill-designed serialized form can be crippling.
如果你接受默認的序列化形式,然后更改了類的內部實現,則會導致與序列化形式不兼容。試圖使用類的舊版本序列化實例,再使用新版本反序列化實例的客戶端(反之亦然)程序將會失敗。當然,可以在維護原始序列化形式的同時更改內部實現(使用 ObjectOutputStream.putFields 或 ObjectInputStream.readFields),但這可能會很困難,并在源代碼中留下明顯的缺陷。如果你選擇使類可序列化,你應該仔細設計一個高質量的序列化形式,以便長期使用([Item-87](/Chapter-12/Chapter-12-Item-87-Consider-using-a-custom-serialized-form.md)、[Item-90](/Chapter-12/Chapter-12-Item-90-Consider-serialization-proxies-instead-of-serialized-instances.md))。這樣做會增加開發的初始成本,但是這樣做是值得的。即使是設計良好的序列化形式,也會限制類的演化;而設計不良的序列化形式,則可能會造成嚴重后果。
A simple example of the constraints on evolution imposed by serializability concerns stream unique identifiers, more commonly known as serial version UIDs. Every serializable class has a unique identification number associated with it. If you do not specify this number by declaring a static final long field named serialVersionUID, the system automatically generates it at runtime by applying a cryptographic hash function (SHA-1) to the structure of the class. This value is affected by the names of the class, the interfaces it implements, and most of its members, including synthetic members generated by the compiler. If you change any of these things, for example, by adding a convenience method, the generated serial version UID changes. If you fail to declare a serial version UID, compatibility will be broken, resulting in an InvalidClassException at runtime.
可序列化會使類的演變受到限制,施加這種約束的一個簡單示例涉及流的唯一標識符,通常稱其為串行版本 UID。每個可序列化的類都有一個與之關聯的唯一標識符。如果你沒有通過聲明一個名為 serialVersionUID 的靜態 final long 字段來指定這個標識符,那么系統將在運行時對類應用加密散列函數(SHA-1)自動生成它。這個值受到類的名稱、實現的接口及其大多數成員(包括編譯器生成的合成成員)的影響。如果你更改了其中任何一項,例如,通過添加一個臨時的方法,生成的序列版本 UID 就會更改。如果你未能聲明序列版本 UID,兼容性將被破壞,從而在運行時導致 InvalidClassException。
**A second cost of implementing Serializable is that it increases the likelihood of bugs and security holes (Item 85).** Normally, objects are created with constructors; serialization is an extralinguistic mechanism for creating objects. Whether you accept the default behavior or override it, deserialization is a “hidden constructor” with all of the same issues as other constructors. Because there is no explicit constructor associated with deserialization, it is easy to forget that you must ensure that it guarantees all of the invariants established by the constructors and that it does not allow an attacker to gain access to the internals of the object under construction. Relying on the default deserialization mechanism can easily leave objects open to invariant corruption and illegal access (Item 88).
**實現 Serializable 接口的第二個代價是,增加了出現 bug 和安全漏洞的可能性(第85項)。** 通常,對象是用構造函數創建的;序列化是一種用于創建對象的超語言機制。無論你接受默認行為還是無視它,反序列化都是一個「隱藏構造函數」,其他構造函數具有的所有問題它都有。由于沒有與反序列化關聯的顯式構造函數,因此很容易忘記必須讓它能夠保證所有的不變量都是由構造函數建立的,并且不允許攻擊者訪問正在構造的對象內部。依賴于默認的反序列化機制,會讓對象輕易地遭受不變性破壞和非法訪問([Item-88](/Chapter-12/Chapter-12-Item-88-Write-readObject-methods-defensively.md))。
**A third cost of implementing Serializable is that it increases the testing burden associated with releasing a new version of a class.** When a serializable class is revised, it is important to check that it is possible to serialize an instance in the new release and deserialize it in old releases, and vice versa. The amount of testing required is thus proportional to the product of the number of serializable classes and the number of releases, which can be large. You must ensure both that the serialization-deserialization process succeeds and that it results in a faithful replica of the original object. The need for testing is reduced if a custom serialized form is carefully designed when the class is first written (Items 87, 90).
**實現 Serializable 接口的第三個代價是,它增加了與發布類的新版本相關的測試負擔。** 當一個可序列化的類被修改時,重要的是檢查是否可以在新版本中序列化一個實例,并在舊版本中反序列化它,反之亦然。因此,所需的測試量與可序列化類的數量及版本的數量成正比,工作量可能很大。你必須確保「序列化-反序列化」過程成功,并確保它生成原始對象的無差錯副本。如果在第一次編寫類時精心設計了自定義序列化形式,那么測試的工作量就會減少([Item-87](/Chapter-12/Chapter-12-Item-87-Consider-using-a-custom-serialized-form.md)、[Item-90](/Chapter-12/Chapter-12-Item-90-Consider-serialization-proxies-instead-of-serialized-instances.md))。
**Implementing Serializable is not a decision to be undertaken lightly.** It is essential if a class is to participate in a framework that relies on Java serialization for object transmission or persistence. Also, it greatly eases the use of a class as a component in another class that must implement Serializable. There are, however, many costs associated with implementing Serializable. Each time you design a class, weigh the costs against the benefits. Historically, value classes such as BigInteger and Instant implemented Serializable, and collection classes did too. Classes representing active entities, such as thread pools, should rarely implement Serializable.
**實現 Serializable 接口并不是一個輕松的決定。** 如果一個類要參與一個框架,該框架依賴于 Java 序列化來進行對象傳輸或持久化,這對于類來說實現 Serializable 接口就是非常重要的。此外,如果類 A 要成為另一個類 B 的一個組件,類 B 必須實現 Serializable 接口,若類 A 可序列化,它就會更易于被使用。然而,與實現 Serializable 相關的代價很多。每次設計一個類時,都要權衡利弊。歷史上,像 BigInteger 和 Instant 這樣的值類實現了 Serializable 接口,集合類也實現了 Serializable 接口。表示活動實體(如線程池)的類很少情況適合實現 Serializable 接口。
**Classes designed for inheritance (Item 19) should rarely implement Serializable, and interfaces should rarely extend it.** Violating this rule places a substantial burden on anyone who extends the class or implements the interface. There are times when it is appropriate to violate the rule. For example, if a class or interface exists primarily to participate in a framework that requires all participants to implement Serializable, then it may make sense for the class or interface to implement or extend Serializable.
**為繼承而設計的類([Item-19](/Chapter-4/Chapter-4-Item-19-Design-and-document-for-inheritance-or-else-prohibit-it.md))很少情況適合實現 Serializable 接口,接口也很少情況適合擴展它。** 違反此規則會給擴展類或實現接口的任何人帶來很大的負擔。有時,違反規則是恰當的。例如,如果一個類或接口的存在主要是為了參與一個要求所有參與者都實現 Serializable 接口的框架,那么類或接口實現或擴展 Serializable 可能是有意義的。
Classes designed for inheritance that do implement Serializable include Throwable and Component. Throwable implements Serializable so RMI can send exceptions from server to client. Component implements Serializable so GUIs can be sent, saved, and restored, but even in the heyday of Swing and AWT, this facility was little-used in practice.
在為了繼承而設計的類中,Throwable 類和 Component 類都實現了 Serializable 接口。正是因為 Throwable 實現了 Serializable 接口,RMI 可以將異常從服務器發送到客戶端;Component 類實現了 Serializable 接口,因此可以發送、保存和恢復 GUI,但即使在 Swing 和 AWT 的鼎盛時期,這個工具在實踐中也很少使用。
If you implement a class with instance fields that is both serializable and extendable, there are several risks to be aware of. If there are any invariants on the instance field values, it is critical to prevent subclasses from overriding the finalize method, which the class can do by overriding finalize and declaring it final. Otherwise, the class will be susceptible to finalizer attacks (Item 8). Finally, if the class has invariants that would be violated if its instance fields were initialized to their default values (zero for integral types, false for boolean, and null for object reference types), you must add this readObjectNoData method:
如果你實現了一個帶有實例字段的類,它同時是可序列化和可擴展的,那么需要注意幾個風險。如果實例字段值上有任何不變量,關鍵是要防止子類覆蓋 finalize 方法,可以通過覆蓋 finalize 并聲明它為 final 來做到。最后,如果類的實例字段初始化為默認值(整數類型為 0,布爾值為 false,對象引用類型為 null),那么必須添加 readObjectNoData 方法:
```
// readObjectNoData for stateful extendable serializable classes
private void readObjectNoData() throws InvalidObjectException {
throw new InvalidObjectException("Stream data required");
}
```
This method was added in Java 4 to cover a corner case involving the addition of a serializable superclass to an existing serializable class [Serialization, 3.5].
這個方法是在 Java 4 中添加的,涉及將可序列化超類添加到現有可序列化類 [Serialization, 3.5] 的特殊情況。
There is one caveat regarding the decision not to implement Serializable. If a class designed for inheritance is not serializable, it may require extra effort to write a serializable subclass. Normal deserialization of such a class requires the superclass to have an accessible parameterless constructor [Serialization, 1.10]. If you don’t provide such a constructor, subclasses are forced to use the serialization proxy pattern (Item 90).
關于不實現 Serializable 的決定,有一個警告。如果為繼承而設計的類不可序列化,則可能需要額外的工作來編寫可序列化的子類。子類的常規反序列化,要求超類具有可訪問的無參數構造函數 [Serialization, 1.10]。如果不提供這樣的構造函數,子類將被迫使用序列化代理模式([Item-90](/Chapter-12/Chapter-12-Item-90-Consider-serialization-proxies-instead-of-serialized-instances.md))。
**Inner classes (Item 24) should not implement Serializable.** They use compiler-generated synthetic fields to store references to enclosing instances and to store values of local variables from enclosing scopes. How these fields correspond to the class definition is unspecified, as are the names of anonymous and local classes. Therefore, the default serialized form of an inner class is illdefined. A static member class can, however, implement Serializable.
**內部類([Item-24](/Chapter-4/Chapter-4-Item-24-Favor-static-member-classes-over-nonstatic.md))不應該實現 Serializable。** 它們使用編譯器生成的合成字段存儲對外圍實例的引用,并存儲來自外圍的局部變量的值。這些字段與類定義的對應關系,就和沒有指定匿名類和局部類的名稱一樣。因此,內部類的默認序列化形式是不確定的。但是,靜態成員類可以實現 Serializable 接口。
To summarize, the ease of implementing Serializable is specious. Unless a class is to be used only in a protected environment where versions will never have to interoperate and servers will never be exposed to untrusted data, implementing Serializable is a serious commitment that should be made with great care. Extra caution is warranted if a class permits inheritance.
總而言之,認為實現 Serializable 接口很簡單這個觀點似是而非。除非類只在受保護的環境中使用,在這種環境中,版本永遠不必互操作,服務器永遠不會暴露不可信的數據,否則實現 Serializable 接口是一項嚴肅的事情,應該非常小心。如果類允許繼承,則更加需要格外小心。
---
**[Back to contents of the chapter(返回章節目錄)](/Chapter-12/Chapter-12-Introduction.md)**
- **Previous Item(上一條目):[Item 85: Prefer alternatives to Java serialization(優先選擇 Java 序列化的替代方案)](/Chapter-12/Chapter-12-Item-85-Prefer-alternatives-to-Java-serialization.md)**
- **Next Item(下一條目):[Item 87: Consider using a custom serialized form(考慮使用自定義序列化形式)](/Chapter-12/Chapter-12-Item-87-Consider-using-a-custom-serialized-form.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(考慮以序列化代理代替序列化實例)