## Chapter 8. Methods(方法)
### Item 50: Make defensive copies when needed(在需要時制作防御性副本)
One thing that makes Java a pleasure to use is that it is a safe language. This means that in the absence of native methods it is immune to buffer overruns, array overruns, wild pointers, and other memory corruption errors that plague unsafe languages such as C and C++. In a safe language, it is possible to write classes and to know with certainty that their invariants will hold, no matter what happens in any other part of the system. This is not possible in languages that treat all of memory as one giant array.
Java 是一種安全的語言,這是它的一大優點。這意味著在沒有本地方法的情況下,它不受緩沖區溢出、數組溢出、非法指針和其他內存損壞錯誤的影響,這些錯誤困擾著 C 和 c++ 等不安全語言。在一種安全的語言中,可以編寫一個類并確定它們的不變量將保持不變,而不管在系統的任何其他部分發生了什么。在將所有內存視為一個巨大數組的語言中,這是不可能的。
Even in a safe language, you aren’t insulated from other classes without some effort on your part. **You must program defensively, with the assumption that clients of your class will do their best to destroy its invariants.** This is increasingly true as people try harder to break the security of systems, but more commonly, your class will have to cope with unexpected behavior resulting from the honest mistakes of well-intentioned programmers. Either way, it is worth taking the time to write classes that are robust in the face of ill-behaved clients.
即使使用一種安全的語言,如果你不付出一些努力,也無法與其他類隔離。**你必須進行防御性的設計,并假定你的類的客戶端會盡最大努力破壞它的不變量。** 隨著人們越來越多地嘗試破壞系統的安全性,這個觀點越來越正確,但更常見的情況是,你的類將不得不處理程序員的無意錯誤所導致的意外行為。無論哪種方式,都值得花時間編寫一個健壯的類來面對行為不軌的客戶端。
While it is impossible for another class to modify an object’s internal state without some assistance from the object, it is surprisingly easy to provide such assistance without meaning to do so. For example, consider the following class, which purports to represent an immutable time period:
雖然如果沒有對象的幫助,另一個類是不可能修改對象的內部狀態的,但是要提供這樣的幫助卻出奇地容易。例如,考慮下面的類,它表示一個不可變的時間段:
```
// Broken "immutable" time period class
public final class Period {
private final Date start;
private final Date end;
/**
* @param start the beginning of the period
* @param end the end of the period; must not precede start
* @throws IllegalArgumentException if start is after end
* @throws NullPointerException if start or end is null
*/
public Period(Date start, Date end) {
if (start.compareTo(end) > 0)
throw new IllegalArgumentException(start + " after " + end);
this.start = start;
this.end = end;
}
public Date start() {
return start;
}
public Date end() {
return end;
}
... // Remainder omitted
}
```
At first glance, this class may appear to be immutable and to enforce the invariant that the start of a period does not follow its end. It is, however, easy to violate this invariant by exploiting the fact that Date is mutable:
乍一看,這個類似乎是不可變的,并且要求一個時間段的開始時間不能在結束時間之后。然而,利用 Date 是可變的這一事實很容易繞過這個約束:
```
// Attack the internals of a Period instance
Date start = new Date();
Date end = new Date();
Period p = new Period(start, end);
end.setYear(78); // Modifies internals of p!
```
As of Java 8, the obvious way to fix this problem is to use Instant (or Local-DateTime or ZonedDateTime) in place of a Date because Instant (and the other java.time classes) are immutable (Item 17). **Date is obsolete and should no longer be used in new code.** That said, the problem still exists: there are times when you’ll have to use mutable value types in your APIs and internal representations, and the techniques discussed in this item are appropriate for those times.
從 Java 8 開始,解決這個問題的典型方法就是使用 Instant(或 Local-DateTime 或 ZonedDateTime)來代替 Date,因為 Instant(和其他時間類)類是不可變的([Item-17](/Chapter-4/Chapter-4-Item-17-Minimize-mutability.md))。**Date 已過時,不應在新代碼中使用。** 盡管如此,問題仍然存在:有時你必須在 API 和內部表示中使用可變值類型,本項目中討論的技術適用于這些情形。
To protect the internals of a Period instance from this sort of attack, **it is essential to make a defensive copy of each mutable parameter to the constructor** and to use the copies as components of the Period instance in place of the originals:
為了保護 Period 實例的內部不受此類攻擊,**必須將每個可變參數的防御性副本復制給構造函數**,并將副本用作 Period 實例的組件,而不是原始組件:
```
// Repaired constructor - makes defensive copies of parameters
public Period(Date start, Date end) {
this.start = new Date(start.getTime());
this.end = new Date(end.getTime());
if (this.start.compareTo(this.end) > 0)
throw new IllegalArgumentException(this.start + " after " + this.end);
}
```
With the new constructor in place, the previous attack will have no effect on the Period instance. Note that **defensive copies are made before checking the validity of the parameters (Item 49), and the validity check is performed on the copies rather than on the originals.** While this may seem unnatural, it is necessary. It protects the class against changes to the parameters from another thread during the window of vulnerability between the time the parameters are checked and the time they are copied. In the computer security community, this is known as a time-of-check/time-of-use or TOCTOU attack [Viega01].
有了新的構造函數,之前的攻擊將不會對 Period 實例產生影響。注意,**防御性副本是在檢查參數的有效性之前制作的([Item-49](/Chapter-8/Chapter-8-Item-49-Check-parameters-for-validity.md)),有效性檢查是在副本上而不是在正本上執行的。** 雖然這看起來不自然,但卻是必要的。在檢查參數和復制參數之間的空窗期,它保護類不受來自其他線程的參數更改的影響。在計算機安全社區里,這被稱為 time-of-check/time-of-use 或 TOCTOU 攻擊 [Viega01]。
Note also that we did not use Date’s clone method to make the defensive copies. Because Date is nonfinal, the clone method is not guaranteed to return an object whose class is java.util.Date: it could return an instance of an untrusted subclass that is specifically designed for malicious mischief. Such a subclass could, for example, record a reference to each instance in a private static list at the time of its creation and allow the attacker to access this list. This would give the attacker free rein over all instances. To prevent this sort of attack, **do not use the clone method to make a defensive copy of a parameter whose type is subclassable by untrusted parties.**
還要注意,我們沒有使用 Date 的 clone 方法來創建防御性副本。因為 Date 不是 final 的,所以不能保證 clone 方法返回一個 java.util.Date 的實例對象:它可以返回一個不受信任子類的實例,這個子類是專門為惡意破壞而設計的。例如,這樣的子類可以在創建時在私有靜態列表中記錄對每個實例的引用,并允許攻擊者訪問這個列表。這將使攻擊者可以自由控制所有實例。為防止此類攻擊,**對可被不受信任方子類化的參數類型,不要使用 clone 方法進行防御性復制。**
While the replacement constructor successfully defends against the previous attack, it is still possible to mutate a Period instance, because its accessors offer access to its mutable internals:
雖然替換構造函數成功地防御了之前的攻擊,但是仍然可以修改 Period 實例,因為它的訪問器提供了對其可變內部結構的訪問:
```
// Second attack on the internals of a Period instance
Date start = new Date();
Date end = new Date();
Period p = new Period(start, end);
p.end().setYear(78); // Modifies internals of p!
```
To defend against the second attack, merely modify the accessors to return defensive copies of mutable internal fields:
要防御第二次攻擊,只需修改訪問器,返回可變內部字段的防御副本:
```
// Repaired accessors - make defensive copies of internal fields
public Date start() {
return new Date(start.getTime());
}
public Date end() {
return new Date(end.getTime());
}
```
With the new constructor and the new accessors in place, Period is truly immutable. No matter how malicious or incompetent a programmer, there is simply no way to violate the invariant that the start of a period does not follow its end (without resorting to extralinguistic means such as native methods and reflection). This is true because there is no way for any class other than Period itself to gain access to either of the mutable fields in a Period instance. These fields are truly encapsulated within the object.
有了新的構造函數和新的訪問器,Period 實際上是不可變的。無論程序員多么惡毒或無能,都不可能違背一個時間段的開始時間不能在結束時間之后這一不變條件(除非使用諸如本地方法和反射之類的外部語言手段)。這是真的,因為除了 Period 本身之外,任何類都無法訪問 Period 實例中的任何可變字段。這些字段真正封裝在對象中。
In the accessors, unlike the constructor, it would be permissible to use the clone method to make the defensive copies. This is so because we know that the class of Period’s internal Date objects is java.util.Date, and not some untrusted subclass. That said, you are generally better off using a constructor or static factory to copy an instance, for reasons outlined in Item 13.
在訪問器中,與構造函數不同,可以使用 clone 方法進行防御性復制。這是因為我們知道 Period 的內部 Date 對象的類是 `java.util.Date`,而不是某個不可信的子類。也就是說,基于 [Item-13](/Chapter-3/Chapter-3-Item-13-Override-clone-judiciously.md) 中列出的原因,一般情況下,最好使用構造函數或靜態工廠來復制實例。
Defensive copying of parameters is not just for immutable classes. Any time you write a method or constructor that stores a reference to a client-provided object in an internal data structure, think about whether the client-provided object is potentially mutable. If it is, think about whether your class could tolerate a change in the object after it was entered into the data structure. If the answer is no, you must defensively copy the object and enter the copy into the data structure in place of the original. For example, if you are considering using a client-provided object reference as an element in an internal Set instance or as a key in an internal Map instance, you should be aware that the invariants of the set or map would be corrupted if the object were modified after it is inserted.
參數的防御性復制不僅適用于不可變類。在編寫方法或構造函數時,如果要在內部數據結構中存儲對客戶端提供的對象的引用,請考慮客戶端提供的對象是否可能是可變的。如果是,請考慮該對象進入數據結構之后,你的類是否能夠容忍該對象發生更改。如果答案是否定的,則必須防御性地復制對象,并將副本輸入到數據結構中,而不是原始正本。舉個例子,如果你正在考慮使用由客戶提供的對象引用作為內部 Set 實例的元素,或者作為內部 Map 實例的鍵, 就應該意識到如果這個對象在插入之后發生改變,Set 或者 Map 的約束條件就會遭到破壞。
The same is true for defensive copying of internal components prior to returning them to clients. Whether or not your class is immutable, you should think twice before returning a reference to an internal component that is mutable. Chances are, you should return a defensive copy. Remember that nonzero-length arrays are always mutable. Therefore, you should always make a defensive copy of an internal array before returning it to a client. Alternatively, you could return an immutable view of the array. Both of these techniques are shown in Item 15.
在將內部組件返回給客戶端之前應對其進行防御性復制也是如此。無論你的類是否是不可變的,在返回對可變內部組件的引用之前,你都應該三思。很有可能,你應該返回一個防御性副本。記住,非零長度數組總是可變的。因此,在將內部數組返回給客戶端之前,應該始終創建一個防御性的副本。或者,你可以返回數組的不可變視圖。這兩種技術都已經在 [Item-15](/Chapter-4/Chapter-4-Item-15-Minimize-the-accessibility-of-classes-and-members.md) 中演示過。
Arguably, the real lesson in all of this is that you should, where possible, use immutable objects as components of your objects so that you that don’t have to worry about defensive copying (Item 17). In the case of our Period example, use Instant (or LocalDateTime or ZonedDateTime), unless you’re using a release prior to Java 8. If you are using an earlier release, one option is to store the primitive long returned by Date.getTime() in place of a Date reference.
可以說,所有這些教訓體現了,在可能的情況下,應該使用不可變對象作為對象的組件,這樣就不必操心防御性復制([Item-17](/Chapter-4/Chapter-4-Item-17-Minimize-mutability.md))。在我們的 Period 示例中,使用 Instant(或 LocalDateTime 或 ZonedDateTime),除非你使用的是 Java 8 之前的版本。如果使用較早的版本,一個選項是存儲 `Date.getTime()` 返回的 long 基本數據類型,而不是 Date 引用。
There may be a performance penalty associated with defensive copying and it isn’t always justified. If a class trusts its caller not to modify an internal component, perhaps because the class and its client are both part of the same package, then it may be appropriate to dispense with defensive copying. Under these circumstances, the class documentation should make it clear that the caller must not modify the affected parameters or return values.
防御性復制可能會帶來性能損失,而且并不總是合理的。如果一個類信任它的調用者不會去修改內部組件,可能是因為類和它的客戶端都是同一個包的一部分,那么就應該避免防御性復制。在這種情況下,類文檔應該表明調用者不能修改受影響的參數或返回值。
Even across package boundaries, it is not always appropriate to make a defensive copy of a mutable parameter before integrating it into an object. There are some methods and constructors whose invocation indicates an explicit handoff of the object referenced by a parameter. When invoking such a method, the client promises that it will no longer modify the object directly. A method or constructor that expects to take ownership of a client-provided mutable object must make this clear in its documentation.
即使跨越包邊界,在將可變參數集成到對象之前對其進行防御性復制也并不總是合適的。有一些方法和構造函數,它們的調用要求參數引用的對象要進行顯式切換。當調用這樣一個方法時,客戶端承諾不再直接修改對象。希望擁有客戶端提供的可變對象所有權的方法或構造函數必須在其文檔中明確說明這一點。
Classes containing methods or constructors whose invocation indicates a transfer of control cannot defend themselves against malicious clients. Such classes are acceptable only when there is mutual trust between a class and its client or when damage to the class’s invariants would harm no one but the client. An example of the latter situation is the wrapper class pattern (Item 18). Depending on the nature of the wrapper class, the client could destroy the class’s invariants by directly accessing an object after it has been wrapped, but this typically would harm only the client.
包含方法或構造函數的類,如果其方法或構造函數的調用需要移交對象的控制權,就不能保護自己免受惡意客戶端的攻擊。只有當一個類和它的客戶端之間相互信任,或者對類的不變量的破壞只會對客戶端造成傷害時,這樣的類才是可接受的。后一種情況的一個例子是包裝類模式([Item-18](/Chapter-4/Chapter-4-Item-18-Favor-composition-over-inheritance.md))。根據包裝類的性質,客戶端可以在包裝對象之后直接訪問對象,從而破壞類的不變量,但這通常只會損害客戶端。
In summary, if a class has mutable components that it gets from or returns to its clients, the class must defensively copy these components. If the cost of the copy would be prohibitive and the class trusts its clients not to modify the components inappropriately, then the defensive copy may be replaced by documentation outlining the client’s responsibility not to modify the affected components.
總而言之,如果一個類具有從客戶端獲取或返回給客戶端的可變組件,則該類必須防御性地復制這些組件。如果復制的成本過高,并且類信任它的客戶端不會不適當地修改組件,那么可以不進行防御性的復制,取而代之的是在文檔中指明客戶端的職責是不得修改受到影響的組件。
---
**[Back to contents of the chapter(返回章節目錄)](/Chapter-8/Chapter-8-Introduction.md)**
- **Previous Item(上一條目):[Item 49: Check parameters for validity(檢查參數的有效性)](/Chapter-8/Chapter-8-Item-49-Check-parameters-for-validity.md)**
- **Next Item(下一條目):[Item 51: Design method signatures carefully(仔細設計方法簽名)](/Chapter-8/Chapter-8-Item-51-Design-method-signatures-carefully.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(考慮以序列化代理代替序列化實例)