## Chapter 7. Lambdas and Streams(λ 表達式和流)
### Item 44: Favor the use of standard functional interfaces(優先使用標準函數式接口)
Now that Java has lambdas, best practices for writing APIs have changed considerably. For example, the Template Method pattern [Gamma95], wherein a subclass overrides a primitive method to specialize the behavior of its superclass, is far less attractive. The modern alternative is to provide a static factory or constructor that accepts a function object to achieve the same effect. More generally, you’ll be writing more constructors and methods that take function objects as parameters. Choosing the right functional parameter type demands care.
現在 Java 已經有了 lambda 表達式,編寫 API 的最佳實踐已經發生了很大的變化。例如,模板方法模式 [Gamma95],其中子類覆蓋基類方法以專門化其超類的行為,就沒有那么有吸引力了。現代的替代方法是提供一個靜態工廠或構造函數,它接受一個函數對象來實現相同的效果。更一般地,你將編寫更多以函數對象為參數的構造函數和方法。選擇正確的函數參數類型需要謹慎。
Consider LinkedHashMap. You can use this class as a cache by overriding its protected removeEldestEntry method, which is invoked by put each time a new key is added to the map. When this method returns true, the map removes its eldest entry, which is passed to the method. The following override allows the map to grow to one hundred entries and then deletes the eldest entry each time a new key is added, maintaining the hundred most recent entries:
考慮 LinkedHashMap。你可以通過覆蓋受保護的 removeEldestEntry 方法將該類用作緩存,每當向映射添加新鍵時,put 都會調用該方法。當該方法返回 true 時,映射將刪除傳遞給該方法的最老條目。下面的覆蓋允許映射增長到 100 個條目,然后在每次添加新鍵時刪除最老的條目,維護 100 個最近的條目:
```
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
return size() > 100;
}
```
This technique works fine, but you can do much better with lambdas. If LinkedHashMap were written today, it would have a static factory or constructor that took a function object. Looking at the declaration for removeEldestEntry, you might think that the function object should take a `Map.Entry<K,V>` and return a boolean, but that wouldn’t quite do it: The removeEldestEntry method calls size() to get the number of entries in the map, which works because removeEldestEntry is an instance method on the map. The function object that you pass to the constructor is not an instance method on the map and can’t capture it because the map doesn’t exist yet when its factory or constructor is invoked. Thus, the map must pass itself to the function object, which must therefore take the map on input as well as its eldest entry. If you were to declare such a functional interface, it would look something like this:
這種技術工作得很好,但是使用 lambda 表達式可以做得更好。如果 LinkedHashMap 是現在編寫的,它將有一個靜態工廠或構造函數,它接受一個函數對象。看著 removeEldestEntry 的定義,你可能會認為這個函數對象應該 `Map.Entry<K,V>` 和返回一個布爾值,但不會完全做到:removeEldestEntry 方法調用 `size()` 地圖中的條目的數量,這工作,因為 removeEldestEntry 在 Map 上是一個實例方法。傳遞給構造函數的函數對象不是 Map 上的實例方法,無法捕獲它,因為在調用 Map 的工廠或構造函數時,Map 還不存在。因此,Map 必須將自身傳遞給函數對象,函數對象因此必須在輸入端及其最老的條目上接受 Map。如果要聲明這樣一個函數式接口,它看起來是這樣的:
```
// Unnecessary functional interface; use a standard one instead.
@FunctionalInterface interface EldestEntryRemovalFunction<K,V>{
boolean remove(Map<K,V> map, Map.Entry<K,V> eldest);
}
```
This interface would work fine, but you shouldn’t use it, because you don’t need to declare a new interface for this purpose. The java.util.function package provides a large collection of standard functional interfaces for your use. **If one of the standard functional interfaces does the job, you should generally use it in preference to a purpose-built functional interface.** This will make your API easier to learn, by reducing its conceptual surface area, and will provide significant interoperability benefits, as many of the standard functional interfaces provide useful default methods. The Predicate interface, for instance, provides methods to combine predicates. In the case of our LinkedHashMap example, the standard `BiPredicate<Map<K,V>`, `Map.Entry<K,V>>` interface should be used in preference to a custom EldestEntryRemovalFunction interface.
這個接口可以很好地工作,但是你不應該使用它,因為你不需要為此聲明一個新接口。`java.util.function` 包提供了大量的標準函數接口供你使用。**如果一個標準的函數式接口可以完成這項工作,那么你通常應該優先使用它,而不是使用專門構建的函數式接口。** 通過減少 API 的概念表面積,這將使你的 API 更容易學習,并將提供顯著的互操作性優勢,因為許多標準函數式接口提供了有用的默認方法。例如,Predicate 接口提供了組合謂詞的方法。在我們的 LinkedHashMap 示例中,應該優先使用標準的 `BiPredicate<Map<K,V>`、`Map.Entry<K,V>>` 接口,而不是定制的 EldestEntryRemovalFunction 接口。
There are forty-three interfaces in java.util.Function. You can’t be expected to remember them all, but if you remember six basic interfaces, you can derive the rest when you need them. The basic interfaces operate on object reference types. The Operator interfaces represent functions whose result and argument types are the same. The Predicate interface represents a function that takes an argument and returns a boolean. The Function interface represents a function whose argument and return types differ. The Supplier interface represents a function that takes no arguments and returns (or “supplies”) a value. Finally, Consumer represents a function that takes an argument and returns nothing, essentially consuming its argument. The six basic functional interfaces are summarized below:
**譯注:原文筆誤,應為 `java.util.function`**
`java.util.function` 中有 43 個接口。不能期望你記住所有的接口,但是如果你記住了 6 個基本接口,那么你可以在需要時派生出其余的接口。基本接口操作對象引用類型。Operator 接口表示結果和參數類型相同的函數。Predicate 接口表示接受參數并返回布爾值的函數。Function 接口表示參數和返回類型不同的函數。Supplier 接口表示一個不接受參數并返回(或「供應」)值的函數。最后,Consumer 表示一個函數,該函數接受一個參數,但不返回任何內容,本質上是使用它的參數。六個基本的函數式接口總結如下:
| Interface | Function Signature | Example |
|:-------:|:-------:|:-------:|
| `UnaryOperator<T>` | `T apply(T t)` | `String::toLowerCase` |
| `BinaryOperator<T>` | `T apply(T t1, T t2)` | `BigInteger::add` |
| `Predicate<T>` | `boolean test(T t)` | `Collection::isEmpty` |
| `Function<T,R>` | `R apply(T t)` | `Arrays::asList` |
| `Supplier<T>` | `T get()` | `Instant::now` |
| `Consumer<T>` | `void accept(T t)` | `System.out::println` |
There are also three variants of each of the six basic interfaces to operate on the primitive types int, long, and double. Their names are derived from the basic interfaces by prefixing them with a primitive type. So, for example, a predicate that takes an int is an IntPredicate, and a binary operator that takes two long values and returns a long is a LongBinaryOperator. None of these variant types is parameterized except for the Function variants, which are parameterized by return type. For example, `LongFunction<int[]>` takes a long and returns an int[].
還有 6 個基本接口的 3 個變體,用于操作基本類型 int、long 和 double。它們的名稱是通過在基本接口前面加上基本類型前綴而派生出來的。例如,一個接受 int 的 Predicate 就是一個 IntPredicate,一個接受兩個 long 值并返回一個 long 的二元操作符就是一個 LongBinaryOperator。除了由返回類型參數化的函數變量外,這些變量類型都不是參數化的。例如,`LongFunction<int[]>` 使用 long 并返回一個 int[]。
There are nine additional variants of the Function interface, for use when the result type is primitive. The source and result types always differ, because a function from a type to itself is a UnaryOperator. If both the source and result types are primitive, prefix Function with SrcToResult, for example LongToIntFunction (six variants). If the source is a primitive and the result is an object reference, prefix Function with `<Src>ToObj`, for example DoubleToObjFunction (three variants).
Function 接口還有 9 個額外的變體,在結果類型為基本數據類型時使用。源類型和結果類型總是不同的,因為不同類型的函數本身都是 UnaryOperator。如果源類型和結果類型都是基本數據類型,則使用帶有 SrcToResult 的前綴函數,例如 LongToIntFunction(六個變體)。如果源是一個基本數據類型,而結果是一個對象引用,則使用帶前綴 `<Src>ToObj` 的 Function 接口,例如 DoubleToObjFunction(三個變體)。
There are two-argument versions of the three basic functional interfaces for which it makes sense to have them: `BiPredicate<T,U>`, `BiFunction<T,U,R>`, and `BiConsumer<T,U>`. There are also BiFunction variants returning the three relevant primitive types: `ToIntBiFunction<T,U>`, `ToLongBiFunction<T,U>`, and `ToDoubleBiFunction<T,U>`. There are two-argument variants of Consumer that take one object reference and one primitive type: `ObjDoubleConsumer<T>`, `ObjIntConsumer<T>`, and `ObjLongConsumer<T>`. In total, there are nine two-argument versions of the basic interfaces.
三個基本函數式接口有兩個參數版本,使用它們是有意義的:`BiPredicate<T,U>`、`BiFunction<T,U,R>`、`BiConsumer<T,U>`。也有 BiFunction 變體返回三個相關的基本類型:`ToIntBiFunction<T,U>`、 `ToLongBiFunction<T,U>`、`ToDoubleBiFunction<T,U>`。Consumer 有兩個參數變體,它們接受一個對象引用和一個基本類型:`ObjDoubleConsumer<T>`、`ObjIntConsumer<T>`、`ObjLongConsumer<T>`。總共有9個基本接口的雙參數版本。
Finally, there is the BooleanSupplier interface, a variant of Supplier that returns boolean values. This is the only explicit mention of the boolean type in any of the standard functional interface names, but boolean return values are supported via Predicate and its four variant forms. The BooleanSupplier interface and the forty-two interfaces described in the previous paragraphs account for all forty-three standard functional interfaces. Admittedly, this is a lot to swallow, and not terribly orthogonal. On the other hand, the bulk of the functional interfaces that you’ll need have been written for you and their names are regular enough that you shouldn’t have too much trouble coming up with one when you need it.
最后是 BooleanSupplier 接口,它是 Supplier 的一個變體,返回布爾值。這是在任何標準函數接口名稱中唯一顯式提到布爾類型的地方,但是通過 Predicate 及其四種變體形式支持布爾返回值。前面描述的 BooleanSupplier 接口和 42 個接口占了全部 43 個標準函數式接口。不可否認,這有很多東西需要消化,而且不是非常直觀。另一方面,你將需要的大部分函數式接口都是為你編寫的,并且它們的名稱足夠常規,因此在需要時你應該不會遇到太多麻煩。
Most of the standard functional interfaces exist only to provide support for primitive types. **Don’t be tempted to use basic functional interfaces with boxed primitives instead of primitive functional interfaces.** While it works, it violates the advice of Item 61, “prefer primitive types to boxed primitives.” The performance consequences of using boxed primitives for bulk operations can be deadly.
大多數標準函數式接口的存在只是為了提供對基本類型的支持。**不要嘗試使用帶有包裝類的基本函數式接口,而不是使用基本類型函數式接口。** 當它工作時,它違反了 [Item-61](/Chapter-9/Chapter-9-Item-61-Prefer-primitive-types-to-boxed-primitives.md) 的建議,“與盒裝原語相比,更喜歡原語類型”。在批量操作中使用裝箱原語的性能后果可能是致命的。
Now you know that you should typically use standard functional interfaces in preference to writing your own. But when should you write your own? Of course you need to write your own if none of the standard ones does what you need, for example if you require a predicate that takes three parameters, or one that throws a checked exception. But there are times you should write your own functional interface even when one of the standard ones is structurally identical.
現在你知道,與編寫自己的接口相比,通常應該使用標準的函數式接口。但是你應該什么時候寫你自己的呢?當然,如果標準的函數式接口都不能滿足你的需要,那么你需要自行編寫,例如,如果你需要一個接受三個參數的 Predicate,或者一個拋出已檢查異常的 Predicate。但是有時候你應該編寫自己的函數接口,即使其中一個標準接口在結構上是相同的。
Consider our old friend `Comparator<T>`, which is structurally identical to the `ToIntBiFunction<T,T>` interface. Even if the latter interface had existed when the former was added to the libraries, it would have been wrong to use it. There are several reasons that Comparator deserves its own interface. First, its name provides excellent documentation every time it is used in an API, and it’s used a lot. Second, the Comparator interface has strong requirements on what constitutes a valid instance, which comprise its general contract. By implementing the interface, you are pledging to adhere to its contract. Third, the interface is heavily outfitted with useful default methods to transform and combine comparators.
考慮我們的老朋友 `Comparator<T>`,它在結構上與 `ToIntBiFunction<T,T>` 接口相同。即使后者接口在將前者添加到庫時已經存在,使用它也是錯誤的。有幾個原因說明比較器應該有自己的接口。首先,每次在 API 中使用 Comparator 時,它的名稱都提供了優秀的文檔,而且它的使用非常頻繁。通過實現接口,你保證遵守其契約。第三,該接口大量配備了用于轉換和組合比較器的有用默認方法。
You should seriously consider writing a purpose-built functional interface in preference to using a standard one if you need a functional interface that shares one or more of the following characteristics with Comparator:
如果你需要與 Comparator 共享以下一個或多個特性的函數式接口,那么你應該認真考慮編寫一個專用的函數式接口,而不是使用標準接口:
- It will be commonly used and could benefit from a descriptive name.
它將被廣泛使用,并且可以從描述性名稱中獲益。
- It has a strong contract associated with it.
它有一個強有力的約定。
- It would benefit from custom default methods.
它將受益于自定義默認方法。
If you elect to write your own functional interface, remember that it’s an interface and hence should be designed with great care (Item 21).
如果你選擇編寫自己的函數式接口,請記住這是一個接口,因此應該非常小心地設計它([Item-21](/Chapter-4/Chapter-4-Item-21-Design-interfaces-for-posterity.md))。
Notice that the EldestEntryRemovalFunction interface (page 199) is labeled with the @FunctionalInterface annotation. This annotation type is similar in spirit to @Override. It is a statement of programmer intent that serves three purposes: it tells readers of the class and its documentation that the interface was designed to enable lambdas; it keeps you honest because the interface won’t compile unless it has exactly one abstract method; and it prevents maintainers from accidentally adding abstract methods to the interface as it evolves. **Always annotate your functional interfaces with the @FunctionalInterface annotation.**
注意 EldestEntryRemovalFunction 接口(第199頁)使用 `@FunctionalInterface` 注釋進行標記。這種注釋類型在本質上類似于 `@Override`。它是程序員意圖的聲明,有三個目的:它告訴類及其文檔的讀者,接口的設計是為了啟用 lambda 表達式;它使你保持誠實,因為接口不會編譯,除非它只有一個抽象方法;它還可以防止維護者在接口發展過程中意外地向接口添加抽象方法。**總是用 `@FunctionalInterface` 注釋你的函數接口。**
A final point should be made concerning the use of functional interfaces in APIs. Do not provide a method with multiple overloadings that take different functional interfaces in the same argument position if it could create a possible ambiguity in the client. This is not just a theoretical problem. The submit method of ExecutorService can take either a `Callable<T>` or a Runnable, and it is possible to write a client program that requires a cast to indicate the correct overloading (Item 52). The easiest way to avoid this problem is not to write overloadings that take different functional interfaces in the same argument position. This is a special case of the advice in Item 52, “use overloading judiciously.”
最后一點應該是關于 API 中函數式接口的使用。不要提供具有多個重載的方法,這些方法采用相同參數位置的不同函數式接口,否則會在客戶機中造成可能的歧義。這不僅僅是一個理論問題。ExecutorService 的 submit 方法可以是 `Callable<T>` 級的,也可以是 Runnable 的,并且可以編寫一個客戶端程序,它需要一個類型轉換來指示正確的重載(Item 52)。避免此問題的最簡單方法是不要編寫將不同函數式接口放在相同參數位置的重載。這是 [Item-52](/Chapter-8/Chapter-8-Item-52-Use-overloading-judiciously.md) 「明智地使用過載」建議的一個特例。
In summary, now that Java has lambdas, it is imperative that you design your APIs with lambdas in mind. Accept functional interface types on input and return them on output. It is generally best to use the standard interfaces provided in java.util.function.Function, but keep your eyes open for the relatively rare cases where you would be better off writing your own functional interface.
總之,既然 Java 已經有了 lambda 表達式,你必須在設計 API 時考慮 lambda 表達式。在輸入時接受函數式接口類型,在輸出時返回它們。一般情況下,最好使用 `java.util.function` 中提供的標準函數式接口,但請注意比較少見的一些情況,在這種情況下,你最好編寫自己的函數式接口。
---
**[Back to contents of the chapter(返回章節目錄)](/Chapter-7/Chapter-7-Introduction.md)**
- **Previous Item(上一條目):[Item 43: Prefer method references to lambdas(方法引用優于 λ 表達式)](/Chapter-7/Chapter-7-Item-43-Prefer-method-references-to-lambdas.md)**
- **Next Item(下一條目):[Item 45: Use streams judiciously(明智地使用流)](/Chapter-7/Chapter-7-Item-45-Use-streams-judiciously.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(考慮以序列化代理代替序列化實例)