` 作为输入参数时,应该通过使用边界通配符类型(bound wildcard type [第 31 条][item31])限制其工厂的类型参数,这样客户端就可以传入创建指定类型的子类型的工厂。比如下面这个例子,使用客户端提供的工厂来生产 tile (瓷砖),从而制作 mosaic(马赛克):
55 |
56 | ```java
57 | Mosaic create(Supplier extends Tile> tileFactory) { ... }
58 | ```
59 |
60 | 尽管依赖注入大大提高了程序的灵活性和可测试性,但它可能会使通常包含数千个依赖项的大型项目变得混乱 。这种混乱可以通过使用依赖注入框架(dependency injection framework)来消除,比如 [Dagger](http://square.github.io/dagger/),[Guice](https://github.com/google/guice) 或 [Spring](https://projects.spring.io/spring-framework/)。本书不讨论这些框架的用法,但请注意,这些框架非常适用于为手动实现依赖注入设计的 API。
61 |
62 | 总而言之,不要使用单例或静态工具类来实现那些依赖于一个或多个底层资源且其行为会被资源影响的类,也不要让类直接创建这些资源。相反,应该把资源或创建资源的工厂传递到构造器(或静态工厂或构建器)中。依赖注入的使用会大大提高类的灵活性、可重用性和可测试性。
63 |
64 | [item2]: ./第%202%20条:遇到多个构造器参数时要考虑用构建器.md "第 02 条:遇到多个构造器参数时要考虑用构建器.md"
65 | [item4]: ./第%204%20条:通过私有构造器强化不可实例化能力.md "第 4 条:通过私有构造器强化不可实例化能力"
66 | [item17]: url "在未来填入第 17 条的 url,否则无法进行跳转"
67 | [item31]: url "在未来填入第 31 条的 url,否则无法进行跳转"
68 |
69 | > 翻译:Inger
70 | >
71 | > 校正:Angus
72 |
--------------------------------------------------------------------------------
/2.创建和销毁对象/第 6 条:避免创建不必要的对象.md:
--------------------------------------------------------------------------------
1 | # 第 6 条:避免创建不必要的对象
2 |
3 | 一般而言,最好能重用一个对象,而不是在每次需要时~~就~~创建一个功能相同的新对象。重用方式既快速,也更流行。如果一个对象是不可变的(immutable [第 17 条][item17]),那它总是可以被重用。
4 |
5 | 作为一个极端的反面例子,考虑下面的语句:
6 |
7 | ```java
8 | String s = new String("bikini"); // 千万不要这么做!
9 | ```
10 |
11 | 该语句在每次执行时都创建一个新的 `String` 实例,但是这些对象的创建都是不必要的。传递给 `String` 构造器的参数(`"bikini"`) 本身就是一个 `String` 实例,功能上与构造器创建的所有对象相同。如果这种用法出现在循环或频繁调用的方法中,就会创建成千上百万个不必要的 `String` 实例。
12 |
13 | 改进后的版本如下:
14 |
15 | ```java
16 | String s = "bikini";
17 | ```
18 |
19 | 这个版本只使用了一个 `String` 实例,而不是在每次执行时都创建一个新的实例。此外,它可以保证,对于所有在同一虚拟机中运行的代码,只要它们包含相同的字符串字面量 [JLS, 3.10.5],该对象就会被重用。
20 |
21 | 对于同时提供了静态工厂方法([第 1 条][item1])和构造器的不可变类,通常可以使用静态工厂方法而不是构造器来避免创建不必要的对象。譬如,工厂方法 `Boolean.valueOf(String)` 就比构造器 `Boolean(String)` 更可取,后者在 Java 9 中已被弃用。构造器在每次被调用时都会创建一个新的对象,而工厂方法则从来没有要求这样做,事实上也不会。除了可以重用不可变对象之外,也可以重用那些已知不会被修改的可变对象。
22 |
23 | 某些对象的创建要比其他对象花费的代价高得多。如果反复需要这样一个“创建代价高昂的对象”(expensive object),那最好将其缓存以供重用。不幸的是,在创建这样的对象时,并不总是显而易见的。假设你要编写一个方法来判定一段字符串是否是有效的罗马数字。下面是使用正则表达式来进行此操作的最简单的方法:
24 |
25 | ```java
26 | // 性能还可以被大大提高!
27 | static boolean isRomanNumeral(String s) {
28 | return s.matches("^(?=.)M*(C[MD]|D?C{0,3})"
29 | + "(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$");
30 | }
31 | ```
32 |
33 | 这个实现的问题在于它依赖 `String.matches` 方法。**虽然 `String.matches` 是检查字符串是否与正则表达式匹配的最简单方法,但是它不适合在性能关键的情况下重复使用。**问题在于,`String.matches` 方法会在内部为正则表达式创建一个 `Pattern` 实例,但只使用它一次,之后它就具备了被垃圾回收的资格。创建一个 `Pattern` 实例代价高昂,因为需要将正则表达式编译成一个有限状态机。
34 |
35 | 为了提高性能,可以显式地将正则表达式编译为一个 `Pattern` 实例(它是不可变的),将其作为类初始化的一部分,对它进行缓存,并在每次调用 `isRomanNumeral` 方法时重用同一个实例:
36 |
37 | ```java
38 | // 重用创建代价高昂的对象以提高性能
39 | public class RomanNumerals {
40 | private static final Pattern ROMAN = Pattern.compile(
41 | "^(?=.)M*(C[MD]|D?C{0,3})"
42 | + "(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$");
43 |
44 | static boolean isRomanNumeral(String s) {
45 | return ROMAN.matcher(s).matches();
46 | }
47 | }
48 | ```
49 |
50 | 如果经常调用,改进版的 `isRomanNumeral` 方法将提供显著的性能收益。在我的机器上,原始版本处理 8 字符的输入字符串花费了 1.1µs,而改进版本只需要 0.17µs,快了将近 6.5 倍。不仅性能得到了改善,而且清晰度也得到了提高。为不可见的 `Pattern` 实例创建静态 final 字段允许我们给它一个名称,而这个名称要比正则表达式本身可读性强得多。
51 |
52 | 如果包含改进版本的 `isRomanNumeral` 方法的类被初始化,但该方法从未被调用,那么 `ROMAN` 字段将不必要地被初始化。可以通过在第一次调用 `isRomanNumeral` 方法时,延迟初始化(lazily initializing)字段([第 83 条](item83))的方式来避免不必要地初始化,但是不建议这样做。延迟初始化通常会出现这样的情况,它会使实现复杂化,并且不会带来可观的性能改进([第 67 条](item67))。
53 |
54 | 当一个对象是不可变的,显然它可以被安全地重用,但是在其他情况下,就不那么明显,甚至违反直觉。考虑适配器(adapter),也称为视图(view)的情况 [[Gamma95](#Gamma95)]。适配器是指提供了可替代的接口,来代表支撑对象(backing object)的对象。由于适配器除了它的支撑对象之外没有其他状态,所以不需要为给定对象创建给定适配器的多个实例。
55 |
56 | 例如,`Map` 接口的 `keySet` 方法会返回 `Map` 对象的一个 `Set` 视图,其中包含该 `map` 中所有的 `key`。初看起来,好像每次对 `keySet` 方法的调用都要创建一个新的 `Set` 实例,但事实上,对于一个给定的 `Map` 对象,每次对 `keySet` 方法的调用都可能返回相同的 `Set` 实例。虽然返回的 `Set` 实例通常是可变的,但是所有返回的对象在功能上是等同的:当其中一个返回对象发生变化时,其他的对象也会发生变化,因为它们都由相同的 `Map` 实例所支撑。虽然创建多个 `keySet` 视图对象的实例在很大程度上没有害处,但这样做既没有必要,也没有好处。
57 |
58 | 另一种会创建不必要对象的方法是自动装箱(autoboxing),它允许程序员将基本类型和基本类型的包装类型(boxed primitive type)混用,按需进行自动装箱和拆箱。**自动装箱机制使得基本类型和包装类型之间的差别变得模糊起来,但并没有完全消除**。它们在语义上还有微妙的差异,在性能上也有比较明显的差别([第 61 条](item61))。考虑下面的方法,它要计算所有正整数的和。为了做到这一点,程序必须使用 `long` 进行运算,因为 `int` 没有大到足以容纳所有 `int` 正值的总和:
59 |
60 | ```java
61 | // 出奇的慢!你能发现对象的创建吗?
62 | private static long sum() {
63 | Long sum = 0L;
64 |
65 | for (long i = 0; i <= Integer.MAX_VALUE; i++)
66 | sum += i;
67 |
68 | return sum;
69 | }
70 | ```
71 |
72 | 这段程序得到了正确的答案,但由于一个字符的排版错误,它比它应该达到的运算速度要慢得多。变量 `sum` 被声明为 `Long` 而不是 `long`,这意味着程序构造了大约 $2^{31}$ 个不必要的 `Long` 实例(大约在每次将 `long i` 添加到 `Long sum` 中时构造一个)。在我的机器上,将 `sum` 的声明从 `Long` 改为 `long` 会将运行时间从 6.3 秒减少到 0.59 秒。结论很明显:**要优先使用基本类型而不是基本类型的包装类型,当心无意识的自动装箱。**
73 |
74 | 不要误解为本条目所介绍的内容暗示着“对象的创建代价高昂,应该尽可能避免”。相反,小对象的构造器只做少量的显式工作,因而小对象的创建和回收成本是非常廉价的,特别在现代的 JVM 实现上更是如此。通过创建附加的对象来提升程序的清晰度、简洁性或功能,这通常是件好事。
75 |
76 | 反之,通过维护自己的对象池(object pool)来避免创建对象不是个明智的做法,除非池中的对象是非常重量级的。正确使用对象池的典型例子是数据库连接。建立连接的成本非常高,因此重用这些对象是有意义的。但是,一般来说,维护自己的对象池会把代码弄得很乱,增加内存占用(memory footprint),并会损害性能。现代的 JVM 实现具有高度优化的垃圾回收器,其性能很容易胜过轻量级对象池的性能。
77 |
78 | 与本条目对应的是[第 50 条][item50]中有关保护性拷贝的内容(defensive copying)。本条目提及“当你应该重用现有的对象时,不要创建新的对象”,而[第 50 条][item50]则说“当你应该创建新对象的时候,不要重用现有的对象”。注意,在提倡使用保护性拷贝的时候,因重用对象的付出的代价要远远大于不必要地创建重复对象所付出的代价。未能在需要的地方实施保护性拷贝,将会导致潜在的错误和安全漏洞;而不必要地创建对象只会影响程序的风格和性能。
79 |
80 | [Gamma95]
Gamma, Erich, Richard Helm, Ralph Johnson, and John Vlissides. 1995.
Design Patterns: Elements of Reusable Object-Oriented Software.
Reading, MA: Addison-Wesley. ISBN: 0201633612.
81 |
82 | [item1]: ./第%201%20条:考虑用静态工厂方法代替构造器.md "第 01条:考虑用静态工厂方法代替构造器"
83 | [item17]: url "在未来填入第 17 条的 url,否则无法进行跳转"
84 | [item50]: url "在未来填入第 50 条的 url,否则无法进行跳转"
85 | [item61]: url "在未来填入第 61 条的 url,否则无法进行跳转"
86 | [item67]: url "在未来填入第 67 条的 url,否则无法进行跳转"
87 | [item83]: url "在未来填入第 83 条的 url,否则无法进行跳转"
88 |
89 | > 翻译:Angus
90 | >
91 | > 校对:Inno
92 |
--------------------------------------------------------------------------------
/2.创建和销毁对象/第 7 条:消除过期对象引用.md:
--------------------------------------------------------------------------------
1 | # 第 7 条:消除过期对象引用
2 |
3 | 如果你从诸如 C 或 C++ 这种手动管理内存的语言切换到像 Java 这种具有垃圾回收的语言,会使得你作为程序员的工作变得更加轻松,因为当你使用完对象后,它们会被自动回收。当你第一次体验到垃圾回收机制时,它就像魔法一般。它很容易给你留下这样的印象,认为自己不需要去考虑内存管理这件事,然而事实并非如此 。
4 |
5 | 思考一下下面这个简单的栈实现的例子:
6 |
7 | ```java
8 | // 你可以认出 “内存泄露” 吗?
9 | public class Stack {
10 | private Object[] elements;
11 | private int size = 0;
12 | private static final int DEFAULT_INITIAL_CAPACITY = 16;
13 |
14 | public Stack() {
15 | elements = new Object[DEFAULT_INITIAL_CAPACITY];
16 | }
17 |
18 | public void push(Object e) {
19 | ensureCapacity();
20 | elements[size++] = e;
21 | }
22 |
23 | public Object pop() {
24 | if (size == 0) throw new EmptyStackException();
25 | return elements[--size];
26 | }
27 |
28 | /**
29 | * 确保至少有一个元素的空间,每次数组需要增长时,容量大约增加一倍。
30 | */
31 | private void ensureCapacity() {
32 | if (elements.length == size) elements = Arrays.copyOf(elements, 2 * size + 1);
33 | }
34 | }
35 | ```
36 |
37 | 这个程序没有明显的错误(泛型版本请查看 [第 29 条][item29])。你可以对它进行全面的测试,它都可以出色地通过,但程序中潜藏着一个问题。 不严格地讲,程序中存在着“内存泄漏(memory leak)”,随着垃圾收集器的活动增加或者内存占用增加,程序性能的下降会逐渐表现出来。在极端情况下,这样的内存泄漏会导致磁盘交换(disk paging),甚至出现 OutOfMemoryError错误导致程序失败 ,不过这种失败的情形相对比较少见。
38 |
39 | 那么,程序中哪里发生了内存泄漏呢?如果一个栈先增长大然后缩小,那么垃圾回收机制不会回收那些从栈中弹出来的对象,即使使用栈的程序不再引用这些对象,它们也不会被回收。这是因为栈内部维护着对这些对象的过期引用(*obsolete reference*)。所谓的过期引用,是指永远也不会再被释放的引用。在这个例子中,除了 elements 数组中的“活跃部分(active portion)“,其余的所有引用都是过时的。活跃部分是指下标小于 size 的那些元素。
40 |
41 | 在支持垃圾回收机制语言中,内存泄漏(更恰当地称其为无意识的对象保留(*unintentional object retention*))是很隐蔽的。如果一个对象引用被无意识的保留下来,垃圾回收器不仅仅不会回收这个对象,也不会回收被这个对象引用的所有其它对象。即使只有很少的对象会被无意识地保留下来,也可能会有许许多多的对象被排除在垃圾回收机制之外,这对性能而言有着潜在的巨大影响。
42 |
43 | 这类问题的修复很简单:一旦对象引用过期,就将它们清空。在我们 Stack 类的例子中,一旦一个对象被弹出栈后,指向它的引用很快就会过期,pop 方法的修正版本如下所示:
44 |
45 | ```java
46 | public Object pop() {
47 | if (size == 0)
48 | throw new EmptyStackException();
49 | Object result = elements[--size];
50 | elements[size] = null;
51 | // 消除过期引用
52 | return result;
53 | }
54 | ```
55 |
56 | 清空过期引用的另一个好处是,如果它们之后被错误地解除引用,程序将会立刻崩溃并抛出 NullPointerException 异常 ,而不是悄悄地错误运行下去。尽可能快地检测出程序的错误总是有好处的。
57 |
58 | 当程序员第一次被这种问题所困扰时,他们往往反应过度:对于每个对象的引用,一旦程序不再使用它就立即把它清空。这既没必要,也不可取,因为它会对程序造成不必要地干扰。**清空对象引用应该是个例,而非常规情况**。消除过期引用的最佳方式,就是让包含了引用的变量超出它的作用域。如果你在尽可能紧凑的作用域内定义每个变量,这种情形就会自然而然的发生([第 57 条][Item57])。
59 |
60 | 那么,什么时候应该清空引用呢?Stack 类的哪方面特性使其易受内存泄漏的影响呢?简单来说,问题在于 Stack 类自己管理内存(manage its own memory)。存储池(*storage pool*)包含 elements 数组的元素(对象引用单元,而不是对象本身)。数组的活跃部分(同前面定义的)的元素是已分配的(*allocated*)状态,而数组其余部分则是自由的(free)状态。垃圾回收器并不知道这一点;对垃圾回收器而言,elements 数组中所有的对象引用都是同等有效的。只有程序员知道~~,~~数组的非活跃部分(inactive portion)是无关紧要的。程序员可以高效地把这一情况告知垃圾回收器,一旦数组元素变成非活跃部分的一部分,程序员就手动清空数组元素。
61 |
62 | 一般而言,**无论任何时候一个类自己管理内存,程序员都应该警惕内存泄漏** 。一个元素无论任何时候被释放,该元素中包含的任何对象引用都应当被清空。
63 |
64 | **内存泄漏的另一个常见来源是缓存**。一旦你把一个对象引用放入缓存中,它就会很容易被遗忘掉,并且在它变得无关紧要后很长一段时间内仍然留在缓存中。对于这个问题有多个解决方案。如果你足够幸运的话 ,只要在缓存之外有对某个项的键(key)的引用,就可以实现与缓存项(entry)完全相关的缓存,那么就可以用 WeakHashMap 代替。在缓存中的项过期后,它们将会被自动清除。记住只有当所要的缓存项的生命周期是由该键的外部引用决定而不是值来决定时,WeakHashMap 才是有用的。
65 |
66 | 更常见的情况是,缓存项的可用寿命定义不太清楚。随着时间的推移,缓存项会变得越来越没有价值。在这种情况下,缓存应该不时地清除已经不用的项。这个任务可以由后台线程(可能是 ScheduledThreadPoolExecutor)来完成,或者也可以在向缓存中添加新缓存项时顺便清理。LinkedHashMap 类使用 removeEldestEntry 方法可以轻松实现后者。 对于更复杂的缓存,你可能需要直接使用 java.lang.ref。
67 |
68 | **内存泄露的第三个常见来源是监听器(listener)和其它回调(callback)**。如果你实现了一个 API,客户端在这个 API 中注册回调但未明确撤销它们,那么除非采取一些措施,否则它们就会累积。确保回调被及时垃圾回收的一种方式,就是仅存储对它们的弱引用(weak reference) ,例如,仅将它们存储为 WeakHashMap 中的键。
69 |
70 | 由于内存泄漏通常不会表现为明显的错误,所以它们可能在一个系统中存在多年。往往只有通过仔细的代码检查或者借助被称为 heap profile 的调试工具才能发现。因此,学会在内存泄漏发生之前预测到这样的问题,并阻止它们发生,那是最好不过的了。
71 |
72 | [item29]:url "在未来填入第 29 条的 url,否则无法进行跳转"
73 | [Item57]:url "在未来填入第 57 条的 url,否则无法进行跳转"
74 |
75 | ---
76 |
77 | > 翻译:Inno
78 | >
79 | > 校对:Inger
80 |
--------------------------------------------------------------------------------
/2.创建和销毁对象/第 8 条:避免使用 Finalizer 和 Cleaner 机制.md:
--------------------------------------------------------------------------------
1 | # 第 8 条:避免使用 Finalizer 和 Cleaner 机制
2 |
3 | **Finalizer 具有不可预测性,通常比较危险,并且一般情况下也没有必要使用。** Finalizer 的使用会导致程序行为不稳定,降低性能,还会带来一些可移植性问题。Finalizer 只在很少情况下有用,在本条目的后面部分会做介绍,但根据经验,你应该避免使用它们。从 Java 9 开始,finalizer 已经被弃用了,然而在一些 JAVA 库中依然可以看到它们的身影。Java 9 中替代 finalizer 的是 cleaner。**Cleaner 比 finalizer 危险性低一些,但仍然是不可预测的,会使程序运行缓慢,通常情况下依然没必要使用**。
4 |
5 | 不要把 Java 的 finalizer 或 cleaner 与 C++ 中的析构函数(destructor)当成类似的机制。在 C++ 中,析构函数是回收一个对象所占用资源的标准方法,而且也是是构造函数所必需的对应物。在 Java 中,当一个对象变得不可达时,垃圾回收器会回收与该对象相关联的存储空间,不需要程序员做专门的工作。 C++ 的析构函数也可以回收其他非内存资源,而在 Java 中,一般用 `try-with-resources` 或者 `try-finally` 块达到同样的目的([第 9 条][item9])。
6 |
7 | Finalizer 和 Cleaner 的缺点之一是不能保证它们被及时执行[JLS, 12.6]。从一个对象变得不可达开始到它的 Finalizer 或 Cleaner 被执行,所花费的时间是任意长的。这意味着**不应该在Finalizer 和 Cleaner 中执行任何对时序要求严格(time-critical)的任务**。比如,依靠 Finalizer 和 Cleaner 关闭已打开文件的操作就是严重错误,因为打开文件的描述符是一种很有限的资源。由于 JVM 会延迟执行 Finalizer 和 Cleaner ,所以大量文件会保留在打开状态,当一个程序不能再打开文件时,它可能会运行失败。
8 |
9 | 及时地执行 finalizer 和 cleaner 正是垃圾回收算法的一个主要功能,然而它在不同的 JVM 实现中差别很大。依赖于 finalizer 或 cleaner 执行及时性的程序的表现也可能发现同样的变化。这样的程序在你测试用的 JVM 平台上运行得非常好,而在你最重要的客户的 JVM 平台上却根本无法运行,这种情况是完全有可能发生的。
10 |
11 | 延迟终结过程不仅仅只是一个理论上的问题。给一个类添加 finalizer 会任意地延迟该类实例的回收过程。我的一位同事在调试一个长期运行的 GUI 应用时,程序莫名其妙报了`OutOfMemoryError`的错误然后死掉。我们分析程序死掉的时期发现,应用程序的 finalizer 队列中有成千上万个图形对象(graphics objects)在等着被终结和回收。不幸的是,这个 finalizer 线程的优先级比该程序的其它线程的优先级低,所以这些对象被终结的速度比不上它们进入终结状态的速度。Java 语言规范并不能保证哪个线程会执行 finalizer ,所以除了避免使用 finalizer 之外,没有别的简便方法来避免这类问题。在这方面 cleaner 比 finalizer 好一点,因为类的作者可以控制自己的 cleaner 线程,但是 cleaner 依然会在垃圾回收器的控制下在后台运行,所以也不能保证及时清理。
12 |
13 | Java 语言规范不仅不能保证 finalizer 或 cleaner 的及时执行,它甚至都不能保证它们会被执行。当一个程序终止时,某些已经无法访问的对象的 finalizer 却根本没有执行,这也是完全有可能的情况。所以,**永远不要依靠 finalizer 或 cleaner 来更新重要的持久状态**。比如,使用 finalizer 或 cleaner 释放共享资源(比如数据库)上的持久锁(persistent lock),很容易让整个分布式系统垮掉。
14 |
15 | 不要被 `System.gc` 和 `System.runFinalization` 这两个方法迷惑。它们可能会提高 finalizer 或 cleaner 被执行的几率,但也不保证它们一定会被执行。有两种方法曾经作出这样的保证:`System.runFinalizersOnExit` 和它臭名昭著的兄弟 `Runtime.runFinalizersOnExit` 方法。这些方法都有致命缺陷,并且已经被废弃了数十年[ [ThreadStop](#ThreadStop)]。
16 |
17 | Finalizer 存在另外一个问题,在终结过程中抛出的未被捕获的异常会被忽略,并且对象的终止过程也会终止 [JSL, 12.6]。未捕获的异常会使其他对象处于破坏(a corrupt state)的状态。如果其他线程试图使用~~了~~这些被破坏的对象,将会导致任意不可预测的行为。通常,未被捕获的异常会终止线程并打印出栈轨迹(stack trace),但发生在 finalizer 中却不会这么做——它甚至不会打印出任何一条警告信息。Cleaner 没有这个问题,因为使用 Cleaner 的库可以控制它的线程。
18 |
19 | **使用 finalizers 或 cleaners 会导致严重的性能损失**。在我的机器上创建一个简单的 `AutoCloseable` 对象,使用 `try-with-resources` 来关闭它并让垃圾回收器回收它所用的时间大约是 12ns。使用 finalizer 反而使时间达到了 550 ns。换句话说,使用 finalizer 创建并销毁对象慢了大约 50 倍。主要是因为 finalizer 抑制了高效的垃圾回收。如果你使用 cleaner 来清除类的所有实例,其速度和 finalizer 差不多(在我的机器上清除每个实例所花费的时间大约是 500 ns),但如果你只是把它们作为安全网(safety net)来使用,cleaner 的速度将会快的多。在这种情况下,创建、清除并销毁对象在我的机器上花了 66 ns,这意味着如果你不使用安全网,为了保险你要付出 5 倍(不是50)的代价。
20 |
21 | **Finalizer 还有一个严重的安全问题:它们会打开你的类致使其受到 finalizer 攻击**(finalizer attacks)。Finalizer 攻击背后的思想很简单:如果从构造器或它的序列化等价物 —— `readObject` 和 `readResolve` 方法([第 12 章][Chapter12])中抛出一个异常,那么恶意子类的 Finalizer 就可以在部分构造的对象上运行,而这些对象本应该夭折。这个 finalizer 可以在静态字段中记录一个该对象的引用,从而防止对该对象进行垃圾回收。一旦记录了格式错误的对象(malformed object),在这个早就不该存在的对象上任意调用方法就会变得非常简单。**从构造器中抛出异常应该足以阻止对象的产生;但在 finalizer 存在的情况下,却并非如此。 **这样的攻击会导致可怕的结果。final 类避免了Finalizer 攻击,因为没人能编写 final 类的恶意子类。**为了保护非 final 类不受 finalizer 攻击,可以编写一个不执行任何操作的 finalize 方法 **。
22 |
23 | 那么对于对象中封装的资源确实需要被终止的类,比如文件或线程,除了编写其 finalizer 或 cleaner,你应该怎么做呢?**可以让你的类实现 `AutoCloseable` 接口**,并且要求该类的客户端在每个实例不再被需要时调用 `close` 方法,通常使用 `try-with-resources` 来确保即使出现异常时亦能正常终止([第 9 条][item9])。有个值得注意的细节,实例必须持续跟踪自己是否被关闭:`close` 方法必须在一个域中记录该对象不再是有效的,如果该对象中的其他方法在对象被关闭之后调用,它们就必须检查这个域,并抛出 `IllegalStateException` 异常。
24 |
25 | 那么,finalizer 和 cleaner有没有什么用处呢?它们可能有两种合法用途。第一种用途是当对象的所有者忘记调用前面段落中建议使用的 `close` 方法的时,它们可以作为安全网(safety net)。虽然不能保证 finalizer 或 cleaner 会及时运行(或者根本不运行),但在客户端无法正常结束操作的情况下,迟一点释放资源总比永远不释放要好。如果你正考虑编写这样一个安全网 finalizer,建议你三思是否值得为这种保护付出代价。一些 Java 库中的类,比如 `FileInputStream`、`FileOutputStream`、`ThreadPoolExecutor` 和 `java.sql.Connection` 就把 finalizer 作为了安全网。
26 |
27 | Cleaner 的第二种合法用途与对象的本地对等体有关(native peer)。本地对等体是一个本地对象(native object,非 Java 对象),普通对象通过本地方法(native method)委托给一个本地对象。因为本地对等体不是普通对象,垃圾回收器并不知道它的存在,所以当它的 Java 对等体被回收的时候它不会被回收。假设程序性能是可接受的而且本地对等体不含有关键资源,这样的情况下,finalizer 或 cleaner 或许能派上用场。如果性能不允许或者本地对等体持有必须被及时回收的资源,你应该为类添加前面段落中建议的 `close` 方法。
28 |
29 | Cleaner 使用起来有些棘手。下面用一个简单的 `Room` 类演示一下。假设 `Room` 的对象必须在它被回收前清除。`Room` 类实现了 `AutoCloseable` 接口;事实上,它的自动清除安全网使用的仅仅是 Cleaner 的一个具体实现。与 Finalizer 不同,Cleaner 不会污染类的公共 API:
30 |
31 | ```java
32 | // 一个实现了 AutoCloseable 接口使用 Cleaner 作为安全网的类
33 | public class Room implements AutoCloseable {
34 | private static final Cleaner cleaner = Cleaner.create();
35 |
36 | // 需要清除的资源,不要牵涉到 Room 对象!
37 | private static class State implements Runnable {
38 | int numJunkPiles; // 房间里的垃圾堆数量
39 | State(int numJunkPiles) {
40 | this.numJunkPiles = numJunkPiles;
41 | }
42 |
43 | // 由 close 方法或 Cleaner 调用
44 | @Override
45 | public void run() {
46 | System.out.println("Cleaning room");
47 | numJunkPiles = 0;
48 | }
49 | }
50 | // 这个房间的状态, 与我们定义的 cleanable 共享
51 | private final State state;
52 | // 我们定义的 cleanable. 当可以获得垃圾回收器时清理房间
53 | private final Cleaner.Cleanable cleanable;
54 | public Room(int numJunkPiles) {
55 | state = new State(numJunkPiles);
56 | cleanable = cleaner.register(this, state);
57 | }
58 | @Override
59 | public void close() {
60 | cleanable.clean();
61 | }
62 | }
63 | ```
64 |
65 | 静态内部类 `State` 含有 Cleaner 清理房间所需的资源。本例中,使用 `numJunkPiles` 变量简单的表示房间的脏乱程度。更确切地说,它可能包含了一个指向本地对等体的 `long` 常量指针。`State` 类实现了`Runnable` 接口并且它的 `run` 方法只能被 `Cleanable` 对象调用一次,`Cleanable` 对象是在 `Room` 的构造器中注册 `State` 实例时获得的 。对 `run` 方法的调用将由以下两种情况之一触发:通常是通过调用 `Room` 的 `close` 方法,继而调用 `Cleanable` 的 `clean` 方法 来触发。其次,当一个 `Room` 实例适合进行垃圾回收时,若客户端没有调用它的 `close` 方法,那么 Cleaner 将(有望)调用 `State` 的 `run` 方法。
66 |
67 | 一个 `State` 实例不应该引用其对应的 `Room` 实例,这一点至关重要。否则,将会出现循环引用使 得`Room` 实例变得不可被回收(也不会被自动清理)。因为非静态内部类包含其外部类实例的引用([第 24 条][Item24]),因此,`State` 必须是静态内部类。同样,使用 lambda 表达式也是不可取的,因为它们可以轻易捕获外部类对象的引用。
68 |
69 | 正如前文所述,`Room` 的Cleaner 仅仅是用来作为安全网的。如果客户端把所有 Room 对象的实例化过程包含在 try-with-resource 块中,就不再需要自动清理了。下面这个功能良好的客户端演示了这种方式:
70 |
71 | ```java
72 | public class Adult {
73 | public static void main(String[] args) {
74 | try (Room myRoom = new Room(7)) {
75 | System.out.println("Goodbye");
76 | }
77 | }
78 | }
79 | ```
80 |
81 | 如你所愿,Adult 程序运行后会先打印出“Goodbye”,随后会打印“Cleaning room”。那么对于那些“不打扫自己的房间”,功能不正常的程序是怎么样的呢?
82 |
83 | ```java
84 | public class Teenager{
85 | public static void main(String[] args){
86 | new Room(99);
87 | System.out.println("Peace out");
88 | }
89 | }
90 | ```
91 |
92 | 你或许期望它会先打印出“Peace out”,随后打印出“Cleaning room”,然而在我的机器上,它直接退出,从没有打印过“Cleanning room”。这就是我们之前谈到的不可预测性。Cleaner 的规范指出:“Cleaner 在 `System.exit` 方法执行期间的行为是特定于实现的。关于是否调用清除操作并没有保证。”虽然规范中没有提到,但普通程序退出时也是如此。 在我的机器上,给 `Teenager` 的 `main` 方法添加`System.gc()` 这一行就可以让它在退出程序前打印出 `Cleaning room` ,但不保证能在你的电脑上看到同样的情形。
93 |
94 | 总之,不要使用 Cleaner 或 Java 9 之前的 Finalizer ,除非作为安全网或用来终止非关键的本地资源。即使这样,也要当心不确定性和性能影响。
95 |
96 | [ThreadStop] Why Are Thread.stop, Thread.suspend, Thread.resume and Runtime.runFinalizersOnExit Deprecated? 1999. Sun Microsystems.
97 |
98 | [item9]: ./第%209%20条:try-with-resources%20优于%20try-finally.md "第 9 条:try-with-resources 优于 try-finally"
99 | [item24]: url "在未来填入第 24 条的 url,否贼无法进行跳转"
100 | [chapter12]: url "在未来填入第 12 章的 url,否则无法进行跳转"
101 | [chapter21]: url "在未来填入第 21 章的 url,否则无法进行跳转"
102 |
103 | > 翻译:Inger
104 | >
105 | > 校对:Angus
106 |
107 |
--------------------------------------------------------------------------------
/2.创建和销毁对象/第 9 条:try-with-resources 优于 try-finally.md:
--------------------------------------------------------------------------------
1 | # 第 9 条:try-with-resources 优于 try-finally
2 |
3 | Java 库中包含了许多必须通过调用 `close` 方法手动关闭的资源。包括 `InputStream`,`OutputStream`,和 `java.sql.Connection` 等。但关闭资源常常被客户端所忽视,随之而来的是预料之际严重的性能影响。虽然这些资源中大部分都使用了 finalizer 作为安全网,但 finalizer 并不能很好地起作用([第 8 条][item8])。
4 |
5 | 在过去,try-finally 语句是保证资源被正确关闭的最佳方式 ,即使在遇到异常或返回时依然如此:
6 |
7 | ```java
8 | // try-finally —— 不再是关闭资源的最佳方式!
9 | static String firstLineOfFile(String path) throws IOException {
10 | BufferedReader br = new BufferedReader(new FileReader(path));
11 | try {
12 | return br.readLine();
13 | } finally {
14 | br.close();
15 | }
16 | }
17 | ```
18 |
19 | 这看起来可能还不错,但当你添加第二个资源时,情况就开始变得糟糕起来:
20 |
21 | ```java
22 | // 当使用多个资源时,try-finally _就变得臃肿不堪!
23 | static void copy(String src, String dst) throws IOException {
24 | InputStream in = new FileInputStream(src);
25 | try {
26 | OutputStream out = new FileOutputStream(dst);
27 | try {
28 | byte[] buf = new byte[BUFFER_SIZE];
29 | int n;
30 | while ((n = in.read(buf)) >= 0)
31 | out.write(buf, 0, n);
32 | } finally {
33 | out.close();
34 | }
35 | }
36 | finally {
37 | in.close();
38 | }
39 | }
40 | ```
41 |
42 | 可能难以置信,即使是优秀的程序员,大多数时候也会在这上面犯错。起初,我在《Java 解惑》( Java Puzzlers [[Bloch05](#Bloch05)])的第88 页上就犯了这个错,但多年来都没有人注意到。事实上,在 2007 年的 Java 库中,关于 `close` 方法的使用有三分之二是错误的。
43 |
44 | 如前两个代码块所示,即使在使用 try-finally 语句关闭资源的正确代码中,也有一个不易察觉的缺陷。try 语句块和 finally 语句块中的代码都能够抛出异常。例如,在 `firstLineOfFile` 方法中,对 `readLine` 方法的调用可能会因为底层物理设备的故障而抛出异常,对 `close` 方法的调用也可能会因为同样的原因而失败。 在这种情况下,第二个异常将第一个异常完全掩盖了。异常堆栈跟踪(exception stack trace)中没有第一个异常的记录,这可能会使实际系统中的调试变得非常复杂——通常,为了诊断问题,你想要看到的是第一个异常。 虽然可以编写代码抑制第二个异常来支持第一个异常,但实际上没有人这样做,因为太繁琐了。
45 |
46 | 当 Java 7 引入 try-with-resources 语句后 [JLS,14.20.3],所有的这些问题一并得到了解决。要使用此构造,资源必须实现 `AutoCloseable` 接口,该接口由一个没有返回值(void-returning)的 `close` 方法组成。现在,Java 库和第三方库中的许多类和接口都实现或者扩展了 `AutoCloseable` 接口。如果你编写的类代表那些必须关闭的资源,那么你的类也应该实现 `AutoCloseable` 接口。
47 |
48 | 下面是第一个示例使用 try-with-resources 语句块之后的样子:
49 |
50 | ```java
51 | // try-with-resources —— 关闭资源的最佳方式!
52 | static String firstLineOfFile(String path) throws IOException {
53 | try (BufferedReader br = new BufferedReader(new FileReader(path))) {
54 | return br.readLine();
55 | }
56 | }
57 | ```
58 |
59 | 下面是第二个示例使用 try-with-resources 语句块之后的样子:
60 |
61 | ```java
62 | // try-with-resources 处理多个资源简短且友好!
63 | static void copy(String src, String dst) throws IOException {
64 | try (InputStream in = new FileInputStream(src);
65 | OutputStream out = new FileOutputStream(dst)) {
66 | byte[] buf = new byte[BUFFER_SIZE];
67 | int n;
68 | while ((n = in.read(buf)) >= 0)
69 | out.write(buf, 0, n);
70 | }
71 | }
72 | ```
73 |
74 | try-with-resources 版本不仅比原始版本更短、更容易阅读,而且提供了更好的诊断功能。考虑 `firstLineOfFile` 方法。如果对 `readLine` 方法的调用和对(不可见的)`close` 方法的调用都抛出了异常,那么为了利于前者,后一个异常会被抑制。实际上,为了保留你真正想要看到的异常,这可能会抑制多个异常。这些被抑制的异常并非只是被丢弃;它们还会被打印在堆栈跟踪中,并带有一个表示它们被抑制的符号。你还可以借助在 Java 7 中添加到 `Throwable` 类中的 `getSuppressed` 方法,以编程的方式访问它们。
75 |
76 | 你可以把 catch 子句放在 try-with-resources 语句块中,就像在普通的 try-finally 语句中那样。这允许你对异常进行处理,而不必使用另一层嵌套对代码造成影响。作为一个稍显人为的例子,下面是我们的 `firstLineOfFile` 方法的一个版本,它不会抛出异常,但如果无法打开文件或从中读取内容,它将返回一个默认值:
77 |
78 | ```java
79 | // try-with-resources 带一个 catch 子句
80 | static String firstLineOfFile(String path, String defaultVal) {
81 | try (BufferedReader br = new BufferedReader(
82 | new FileReader(path))) {
83 | return br.readLine();
84 | } catch (IOException e) {
85 | return defaultVal;
86 | }
87 | }
88 | ```
89 |
90 | 结论很明确:在使用必须关闭的资源时,应始终优先使用 try-with-resources 而不是 try-finally。它生成的代码更短更清晰,产生的异常信息也更有用。使用 try-with-resources 语句使得正确编写那些必须被关闭的资源的代码变得容易,而这在使用 try-finally 语句时几乎是不可能的。
91 |
92 | [Bloch05] Bloch, Joshua, and Neal Gafter. 2005. Java Puzzlers: Traps, Pitfalls, and Corner Cases.
93 | Boston: Addison-Wesley.
94 | ISBN: 032133678X.
95 |
96 | [item8]: ./第%208%20条:避免使用%20Finalizer%20和%20Cleaner%20机制.md "第 8 条:避免使用 Finalizer 和 Cleaner 机制"
97 |
98 | >翻译:Angus
99 | >
100 | >校对:Inno
101 |
--------------------------------------------------------------------------------
/2.创建和销毁对象/第二章 创建和销毁对象.md:
--------------------------------------------------------------------------------
1 | # 第二章 创建和销毁对象
2 |
3 | 本章的主题是创建和销毁对象:何时以及如何创建对象、何时以及如何避免创建对象、如何确保及时地销毁它们、以及在对象销毁之前如何管理必须进行的各种清理动。
4 |
5 | ---
6 |
7 | > 翻译:Angus
8 | >
9 | > 校对:Inno
10 |
--------------------------------------------------------------------------------
/3.对所有对象都通用的方法/第 10 条:覆盖 equals 时请遵守通用约定.md:
--------------------------------------------------------------------------------
1 | # 第 10 条:覆盖 equals 时请遵守通用约定
2 |
3 | 覆盖 equals 方法似乎很简单,但是有很多方式会导致错误,并且后果会很严重。防止问题出现的最简单的方式就是不要覆盖 equals 方法,在这种情况下,类的每个实例都只等于自身。如果满足一下任何条件,这样做就是对的:
4 |
5 | + **类的每个实例本质上都是唯一的**。 对于代表活动实体而不是值(value)的类来说确实如此,例如 Thread。Object 提供的 equals 方法实现对于这些类来说正是正确的行为
6 | + **不需要为类提供“逻辑相等(logical quality)”的测试**。例如,java.util.regex.Pattern 覆盖了 equals 方法,以检查两个 Pattern 实例是否确切地代表了相同的正则表达式,但是设计者并不认为客户端会需要或者想要这样的功能。在这种情况下,从 Object 继承的 equals 实现是理想的。
7 | + **一个超类已经覆盖了 equals 方法,并且超类的行为适用于这个类**。例如,大多数 Set 实现从 AbstractSet 继承 equals 实现,List 实现从 AbstractList 继承 equals 实现,Map 实现从 AbstractMap 继承 equals 实现。
8 | + **类是私有或者包级私有的,并且你确定类的 equals 方法绝不会被调用**。如果你是极度规避风险的,你可以覆盖 equals 方法,以确保他不会被意外调用:
9 |
10 | ```java
11 | @Override
12 | public boolean equals(Object o) {
13 | throw new AssertionError(); // 该方法绝不会被调用
14 | }
15 | ```
16 |
17 | 那么,什么时候适合覆盖 equals 方法呢?当一个类具有逻辑相等的概念时(有别于单纯的对象等同),并且超类没有覆盖 equals 方法。这通常是“值类(value class)”的情况。值类是指,诸如 Interger 或 String 这种只是代表一个值的类。使用 equals 方法比较值对象的引用的程序员,期望找出它们是否在逻辑上相等,而不是它们是否引用了同一对象。覆盖 equals 方法不仅仅对满足程序员的预期是必要的,而且还能使实例充当映射表(map)的键(key)或者集合(set) 的元素,使映射表或者集合表现出预期的行为。
18 |
19 | 有一种值类不需要覆盖 equals 方法,该类使用实例控制([第 1 条][item1])来确保每个值最多只有一个对象存在。枚举类型([第 34 条][item34])就属于这种类。对这种类而言,逻辑相等与对象等同是一样的,所以 Object 的 equals 方法等同于逻辑上的 equals 方法。
20 |
21 | 当你覆盖了 equals 方法,你一定依遵循它的通用约定。下面是约定的内容,来自 Object 规范:
22 |
23 | equals 方法实现了等价关系(equivalence relation)。它有下面这些属性:
24 |
25 | + **自反性(reflexive)**。对任何非空引用值 x,`x.equals(x)` 一定返回 true。
26 | + **对称性(symmetric)**。对任何非空引用值 x 和 y,当且仅当 `y.equals(x)` 返回 true 时 `x.equals(y)` 一定返回 true。
27 | + **传递性(transitive)**。对任何非空引用值 x、y、z,如果 `x.equals(y)` 返回 true 并且 `y.equals(z)` 返回 true,那么 `x.equals(z)` 一定返回 true。
28 | + **一致性(consistent)**。对任何非空引用值 x 和 y,如果 equals 的比较操作在对象中所用的信息没有被修改,多次调用 `x.equals(y)` 就会一致地返回 true 或者一致地返回 false。
29 | + 对任何非空引用值 x,`x.equals(null)` 一定返回 false。
30 |
31 | 除非你在数学上有所倾斜,否则这看起来有点恐怖,但是不要忽视它。如果违犯了它,你可能会发现你的程序行为异常或崩溃,并且很难确定故障的根源。用 John Donne 的话说,没有哪个类是孤立的。一个类的实例被频繁的传递给另一个类。许多类,包括所有集合类(collection class),都依赖于传递给它们的对象是否遵循 equals 约定。
32 |
33 | 现在以已经意识到了违反 equals 约定的危险了,接下来让我们详细讨论一下这些约定。好消息是,尽管这些约定看起来很吓人,但实际上并不是很复杂。一旦你理解了它,那么遵循这些约定就不是什么难事了。
34 |
35 | 那么,什么是等价关系呢?不严格地讲,它是把一组元素分割成子集的操作符,这些子集的元素被视为是彼此相等的。这些子集被称为等价类(equivalence class)。要使 equals 方法有用,每个等价类中的所有元素必须可以从用户的角度进行互换。现在让我们依次检查五个要求:
36 |
37 | * **自反性(reflexivity)**—— 第一个要求仅仅说明一个对象必须与它自身相等。很难想象会无意间违反这个约定。如果你违反了它,并在之后为集合(collection)添加了该类的实例,该集合的 contains 方法可能会告诉你,该集合不曾含有你刚刚添加的实例。
38 | * **对称性(symmetry)**—— 第二个要求说明的是任何两个对象对于“它们是否相等”的问题必须保持一致。于第一个约定不同,若无意间违反了该约定,倒不是很难想象。例如,考虑下面这个类,这个类实现了一个不区分大小写的字符串。字符串的大小由 toString 方法保存,但在 equals 方法的比较操作中被忽略:
39 |
40 | ```java
41 | // 违反对称性!
42 | public final class CaseInsensitiveString {
43 | private final String s;
44 |
45 | public CaseInsensitiveString(String s) {
46 | this.s = Objects.requireNonNull(s);
47 | }
48 |
49 | // 违反对称性!
50 | @Override
51 | public boolean equals(Object o) {
52 | if (o instanceof CaseInsensitiveString)
53 | return s.equalsIgnoreCase(((CaseInsensitiveString) o).s);
54 | if (o instanceof String) // 单向互通!
55 | return s.equalsIgnoreCase((String) o);
56 | return false;
57 | }
58 | // ...
59 | // 其余省略
60 | }
61 | ```
62 |
63 | 在这个类中,出于好意的 equals 方法天真地尝试与普通的字符串(String)对象进行互操作。假设我们有一个不区分大小写的字符串和一个普通的字符串:
64 |
65 | ```java
66 | CaseInsensitiveString cis = new CaseInsensitiveString("Polish");
67 | String s = "polish";
68 | ```
69 |
70 | 正如预想的那样,`cis.equals(s)` 返回 `true`。这里的问题在于,虽然 CaseInsensitiveString 类中的 equals 方法知道普通的字符串(String)对象,但是 String 类中的 equals 方法却对不区分大小的字符串浑然不知。因此,`s.equals(cis)` 返回 `false`,显然违反了对称性。假设你把一个不区分大小写的字符串放进了集合里:
71 |
72 | ```java
73 | List list = new ArrayList<>();
74 | list.add(cis);
75 | ```
76 |
77 | 此时 `list.contains(s)` 方法会返回什么结果呢?谁知道呢?在 OpenJDK 的当前实现中,它碰巧返回 `false`,但这只是这个特定实现得出的结果而已。在其它实现中,它可以很容易地返回 true 或者抛出运行时(runtime)异常。**一旦你违反了 equals 约定,当其它对象面对你的对象时,你就不知道这些对象的行为会怎样**。
78 |
79 | 要解决这个问题,只需从 equals 方法中去除与 String 对象进行互操作这个不明智的尝试即可。一旦这样做了,就可以把该方法重构为单个返回语句:
80 |
81 | ```java
82 | @Override
83 | public boolean equals(Object o) {
84 | return o instanceof CaseInsensitiveString
85 | && ((CaseInsensitiveString) o).s.equalsIgnoreCase(s);
86 | }
87 | ```
88 |
89 | **传递性(Transitivity)**—— equals 约定的第三个要求说的是如果一个对象等于第二个对象,并且第二个对象等于第三个对象,那么第一个对象一定与第三个对象相等。同样,不难想象无意中违反这一要求。考虑子类的情形,该子类将新的值组件(value component)添加到其超类中。换句话说,该子类添加了一条影响 equals 方法比较的信息。让我们从一个简单的不可变的二维整数型 Point 类开始:
90 |
91 | ```java
92 | public class Point {
93 | private final int x;
94 | private final int y;
95 |
96 | public Point(int x, int y) {
97 | this.x = x;
98 | this.y = y;
99 | }
100 |
101 | @Override
102 | public boolean equals(Object o) {
103 | if (!(o instanceof Point)) return false;
104 | Point p = (Point) o;
105 | return p.x == x && p.y == y;
106 | }
107 | // ...
108 | // 其余省略
109 | }
110 | ```
111 |
112 | 假设你想扩展这个类,为一个点添加颜色的概念:
113 |
114 | ```java
115 | public class ColorPoint extends Point {
116 | private final Color color;
117 |
118 | public ColorPoint(int x, int y, Color color) {
119 | super(x, y);
120 | this.color = color;
121 | }
122 | // ...
123 | // 其余省略
124 | }
125 | ```
126 |
127 | equals 方法应该是什么样子呢?如果完全省略不写,那么继承自 Point 的实现以及颜色信息会在 equals 的比较中被忽略。虽然这样做并没有违反 equals 约定,但是这样明显是不能被接受的。假设你写了一个 equals 方法,当且仅当它的参数是另一个具有相同位置和颜色的颜色点时,才会返回 true。
128 |
129 | ```java
130 | // 违反对称性!
131 | @Override
132 | public boolean equals(Object o) {
133 | if (!(o instanceof ColorPoint)) return false;
134 | return super.equals(o) && ((ColorPoint) o).color == color;
135 | }
136 | ```
137 |
138 | 这个方法的问题在于,当你比较一个普通点与有色点时可能会得到不同的结果,反之亦然。普通点与有色点比较会忽略颜色,而有色点与普通点比较会始终返回 false,这是因为参数类型是不正确的。为了更具体的描述这个问题,让我们创建一个普通点和一个颜色点:
139 |
140 | ```java
141 | Point p = new Point(1, 2);
142 | ColorPoint cp = new ColorPoint(1, 2, Color.RED);
143 | ```
144 |
145 | 然后,`p.equals(cp)` 会返回 true,而 `cp.equals(p)` 会返回 false。你可能会在 “混合比较” 时,通过让 ColorPoint.equals 忽略颜色来修复这个问题:
146 |
147 | ```java
148 | // 违反传递性
149 | @Override
150 | public boolean equals(Object o) {
151 | if (!(o instanceof Point)) return false;
152 | // 如果 o 是一个普通点,那么做一个色盲比较
153 | if (!(o instanceof ColorPoint)) return o.equals(this);
154 | // 如果 o 是颜色点,做一个完整的比较
155 | return super.equals(o) && ((ColorPoint) o).color == color;
156 | }
157 | ```
158 |
159 | 这种方法确实提供了对称性,但却是在破坏传递性的情况下:
160 |
161 | ```java
162 | ColorPoint p1 = new ColorPoint(1, 2, Color.RED);
163 | Point p2 = new Point(1, 2);
164 | ColorPoint p3 = new ColorPoint(1, 2, Color.BLUE);
165 | ```
166 |
167 | 现在 `p1.equals(p2)` 和 `p2.equals(p3)` 都返回 true,然而 `p1.equals(p3)` 却返回 false,很明显违反了传递性。前两个比较是 “色盲” 比较,而第三个则是考虑了颜色。
168 |
169 | 此外,这种方法还会导致无限递归:假设这有两个 Point 的子类,比如 ColorPoint 和 SmellPoint,每个都有这种 equals 方法。然后调用 `myColorPoint.equals(mySmellPoint)` 将会抛出 StackOverflowError 异常。
170 |
171 | 那么解决方案是什么呢?事实上,这是面向对象语言中等价关系的基本问题。除非您愿意放弃面向对象抽象的好处,否则无法扩展可实例化的类,并在保留 equals 约定的同时添加值组件。
172 |
173 | 你可能听说,通过在 equals 方法中使用 getClass 测试替代 instanceof 测试,可以在扩展可实例化的类并且添加值组件的同时保留 equals 约定:
174 |
175 | ```java
176 | // 违反里氏替换原则
177 | @Override
178 | public boolean equals(Object o) {
179 | if (o == null || o.getClass() != getClass()) return false;
180 | Point p = (Point) o;
181 | return p.x == x && p.y == y;
182 | }
183 | ```
184 |
185 | 只有当它们具有相同的实现类的时候,它们才具有相同对象的效果。这似乎并不差,但是结果是不能接受的:一个 Point 子类的实例仍然是 Point,并且它仍然需要作为一个函数,但是如果你采用这种方法,它就不能这样做。假设我们要写一个方法来判断一个点是否在单位圆上。 下面是可以采用的其中一种方法:
186 |
187 | ```java
188 | // 初始化一个单位圆用来包含单位圆上所有的点
189 | private static final Set unitCircle = Set.of(
190 | new Point(1, 0), new Point(0, 1),
191 | new Point(-1, 0), new Point(0, -1));
192 |
193 | public static boolean onUnitCircle(Point p) {
194 | return unitCircle.contains(p);
195 | }
196 | ```
197 |
198 | 虽然这可能不是实现这个功能最快的方法,不过效果很好。假设你通过某种不添加值组件的方式扩展了 Point,例如让它的构造器记录创建了多少个实例:
199 |
200 | ```java
201 | public class CounterPoint extends Point {
202 | private static final AtomicInteger counter = new AtomicInteger();
203 |
204 | public CounterPoint(int x, int y) {
205 | super(x, y);
206 | counter.incrementAndGet();
207 | }
208 |
209 | public static int numberCreated() {
210 | return counter.get();
211 | }
212 | }
213 | ```
214 |
215 | 里氏替换原则(Liskov substitution principle)说的是,任何一个类型的重要属性也适用于它的子类型,以便任何为这个类型写的方法可以在它的子类型上工作的一样好[[Liskov87](#Liskov87)]。这是我们之前声明的 Point 的子类(比如 CounterPoint)任然是一个 Point 并且必须作为一个整体的正式声明。但是假设我们为 onUnitCircle 方法传递了一个 CounterPoint 实例。假如 Point 类使用了基于 getClass 的 equals 方法,那么不管 CounterPoint 实例的 x 和 y 坐标是什么, onUnitCircle 方法都将返回 false 。之所以如此,是因为像 onUnitCircle 方法所用的 HashSet 这样的集合,利用 equals 方法检验包含条件,没有任何 CounterPoint 实例与任何 Point 对应。然而,如果在 Point 上使用了恰当的基于 instanceof 的 equals 方法,那么当遇到 CounterPoint 时,相同的 onUnitCircle 方法就会工作的很好。
216 |
217 | 虽然没有一种令人满意的方法可以扩展一个可实例化的类并添加值组件,但是这有一个不错的权宜之计(workaround)。遵循 [第 18 条](item18) 的建议 :组合优于继承。不再让 ColorPoint 继承 Point,取而代之的是为 ColorPoint 添加一个私有的 Point 属性,以及一个公有的视图(view)方法([第 6 条](item6)),该方法返回一个与该有色点处相同位置的普通 Point 对象:
218 |
219 | ```java
220 | // 在不违反相等合同的情况下添加值组件
221 | public class ColorPoint {
222 | private final Point point;
223 | private final Color color;
224 |
225 | public ColorPoint(int x, int y, Color color) {
226 | point = new Point(x, y);
227 | this.color = Objects.requireNonNull(color);
228 | }
229 |
230 | /**
231 | * 返回此有色点的点视图(point-view)。
232 | */
233 | public Point asPoint() {
234 | return point;
235 | }
236 |
237 | @Override
238 | public boolean equals(Object o) {
239 | if (!(o instanceof ColorPoint)) return false;
240 | ColorPoint cp = (ColorPoint) o;
241 | return cp.point.equals(point) && cp.color.equals(color);
242 | }
243 | // ...
244 | // 其余省略
245 | }
246 | ```
247 |
248 | 在 Java 平台库中有一些类的确扩展了可实例化类并添加了一个值组件。例如,java.sql.Timestamp 扩展了 java.util.Date 并且添加了一个 nanoseconds 属性。Timestamp 的 equals 方法的确违反了对称性,如果 Timestamp 和 Date 对象在同一个集合或者其他混合方式中使用的话,会导致不稳定的行为。Timestamp 类有一个免责声明,提醒程序员不要混淆 Date 和 Timestamp 对象。只要你将它们分开就不会惹上麻烦,但除此之外没有可以阻止你混淆它们的方法,并且产生的错误可能难以调试。Timestamp 类的行为是错误的,不应该被效仿。
249 |
250 | 值得一提的是,你可以为抽象(abstract)类的子类添加值组件而不用违反 equals 约定。通过遵循 [第 23 条](item23) 的建议,“用类层次(class hierarchies)代替标签类(tagged class)” 来获得类层次结构,这是非常重要的。例如,你有一个没有值组件的抽象类 Shape,一个添加了 radius 属性的子类 Circle,以及一个添加了 length 和 width 属性的子类 Rectangle。只要不可能直接创建超类实例,那么之前出现的种种问题就都不会发生。
251 |
252 | **一致性(consistency)** —— equals 约定的第四个要求是,如果两个对象是相等的,那么它们一定至始至终保持相等,除非某一个对象或者两个都被修改。换句话说,可变对象可以与不同的对象在不同时刻相等,而不可变对象则不行。当你在写一个类时,应该认真考虑该类是否应该不可变([第 17 条](item17))。如果你的结论是应该,那么应确保你的 equals 方法强制满足这一限制:相等的对象永远保持相等,不相等的对象永远保持不相等。
253 |
254 | 无论一个类是否不可变,**千万不要编写依赖不可靠资源的 equals 方法**,如果你违反了这一禁令,那么满足一致性这一要求将是极其困难的。例如,java.net.URL 的 equals 方法依赖与 URL 相关联的主机的 IP 地址的比较。把主机名翻译成 IP 地址需要访问网络,并且随着时间的推移,不能保证都产生相同的结果。这会使得 URL 的 equals 方法违反 equals 约定,在实践中有可能引发一些问题。URL 的 equals 方法的行为是一个大错误,并且不应被效仿。不幸的是,因为兼容性的需要,这并不能被改变。为了避免这类问题,equals 方法应该只对内存驻留对象执行确定性计算。
255 |
256 | **非空性(Non-nullity)** —— 最后一个要求缺少官方的名称,因此我冒昧地称其为 “非空性”,指的是所有对象一定不能等于 null。虽然很难想象在调用 `o.equals(null)` 时意外地返回 true,但是不难想象它会意外地抛出 NullPointerException 异常。这个通用的约定禁止这样类事情的发生。很多类的 equals 方法都通过一个显示的 null 测试来防止这种情况的发生:
257 |
258 | ```java
259 | @Override
260 | public boolean equals(Object o) {
261 | if (o == null)
262 | return false;
263 | // ...
264 | }
265 | ```
266 |
267 | 这个测试是没必要的。为了测试其参数的等同性,equals 方法必须先把参数转换成适当的类型,以便可以调用它的访问方法(accessor),或者访问它的属性。在进行转换之前,equals 方法必须使用使用 instanceof 操作符检查它的参数是否是正确的类型:
268 |
269 | ```java
270 | @Override
271 | public boolean equals(Object o) {
272 | if (!(o instanceof MyType))
273 | return false;
274 | MyType mt = (MyType) o;
275 | // ...
276 | }
277 | ```
278 |
279 | 如果缺少了类型检查并且传递给 equals 方法错误的参数类型,那么 equals 方法就会抛出 ClassCastException,这违反了 equals 约定。如果第一个操作数是 null 的话,不论第二个操作数是什么,instanceof 操作符都会指定返回 false [JLS, 15.20.2]。因此,如果传入了一个 null,类型检查就会返回 false,所以你不需要一个显式的空检查。
280 |
281 | 结合所有要求,得出了以下实现高质量 equals 方法的诀窍:
282 |
283 | 1. **使用 == 操作符来检查 “参数是否是这个对象的引用”**。如果是的话,返回 true。这只是一个性能优化,但如果比较的代价可能很高的话,那么值得这么做。
284 | 2. **使用 instanceof 操作符检查 “参数是否是正确的类型”**。如果不是的话,返回 false。一般说来,所谓 “正确的类型” 是指 equals 方法所在的那个类。有时,它指的是由这个类实现的接口,如果类实现了一个改进 equals 约定的接口,允许在实现了该接口的类之间进行比较,那么就使用接口。集合接口(collection interface,如 Set、List、Map 和 Map.Entry)都具有此属性。
285 | 3. **把参数转换成正确的类型**。因为在强制转换前会用 instanceof 测试,所以保证会成功。
286 | 4. **对类中的每个“关键(significance)”属性,检查参数的属性是否与这个对象的属性相一致**。如果所有的测试都成功了的话,就返回 true,否则返回 false。如果第 2 步中的类型是一个接口,必须会通过接口方法访问参数的属性;如果该类型是一个类,或许能够直接访问其属性,这要依赖它们的可访问性。
287 |
288 | 对于基本类型属性,其类型不是 float 或 double 的话,使用 == 操作符来进行比较;对于对象引用属性,递归地调用 equals 方法来进行比较;对于 float 属性,使用静态的 Float.compare(float, float) 方法来进行比较;对于 double 属性,则使用 Double.compare(double, double) 来进行比较。由于 Float.Nan,-0.0f 和类似的 Double 值的存在,对 float 和 double 属性进行特殊处理就变得有必要了。详细信息请查看 JLS 15.21.1 或者 Float.equals 的文档。当你使用静态的 Float.equals 和 Double.equals 方法来比较 float 和 double 属性时,其性能将会很差。对与数组属性,则要把这些准则应用到每个元素上。如果在数组属性中,每个元素都很重要的话,那么使用 Arrays.equals 方法的其中一种来进行比较。
289 |
290 | 有些对象引用属性可能合法地包含 null。为了防止 NullPointerException 异常的出现,使用静态的 Objects.equals(Object, Object) 方法来检查这些属性是否相等。
291 |
292 | 对于有些类,例如上面的 CaseInsensitiveString 类,属性比较将比简单的等同性测试更加复杂。如果是这种情况,可能会希望保存该属性的一个 “范式(canonical form)”,这样 equals 方法就可以根据这些范式进行低开销的精确比较,而不是高开销的非精确比较。这个技术对于不可变类是最为适用的([第 17 条](item17))。如果对象是可以改变的,那么一定要保持其范式是最新的。
293 |
294 | equals 方法的性能可能受属性的比较顺序的影响。为了获得最佳性能,应该先比较最有可能不一样的属性,或开销最低的属性,最理想的情况是兼具这两种的属性。一定不要比较不是对象逻辑状态的属性,例如使用了 synchronize 操作符的 lock 属性。你不需要比较派生属性(derived field),这些属性可以从 “重要属性(significant field)” 中计算出来,但这样做可以提升equals 方法的性能。如果派生属性相当于整个对象的摘要描述的话,那么在比较失败的时,比较此属性将节省你比较实际数据的开销。例如,假设你有一个 Polygon 类,并且你缓存了该区域。如果两个 Polygon 对象有着不相同的区域,那么就没有必要去比较它们的边和顶点。
295 |
296 | **当你已经编写完成了你的 equals 方法,你需要问你自己三个问题:它是否是对称的?是否是传递的?是否是一致的?**并且不要只是自问,写一个单元测试来进行检查,除非你是用了 AutoValue 来生成你的 equals 方法,在这种情况下,你可以安全地省略这些测试。如果无法遵循某个特性,找出原因,并相应的修改 equals 方法。当然,你的 equals 方法也必须满足其他两个特性(自反性和非空性),但是这两个特性通常会自动满足。
297 |
298 | 在这个简单的 PhoneNumber 类中,展示了根据之前的诀窍构造的 equals 方法:
299 |
300 | ```java
301 | // 一个有着典型的 equals 方法的类
302 | public final class PhoneNumber {
303 | private final short areaCode, prefix, lineNum;
304 |
305 | public PhoneNumber(int areaCode, int prefix, int lineNum) {
306 | this.areaCode = rangeCheck(areaCode, 999, "area code");
307 | this.prefix = rangeCheck(prefix, 999, "prefix");
308 | this.lineNum = rangeCheck(lineNum, 9999, "line num");
309 | }
310 |
311 | private static short rangeCheck(int val, int max, String arg) {
312 | if (val < 0 || val > max) throw new IllegalArgumentException(arg + ": " + val);
313 | return (short) val;
314 | }
315 |
316 | @Override
317 | public boolean equals(Object o) {
318 | if (o == this) return true;
319 | if (!(o instanceof PhoneNumber)) return false;
320 | PhoneNumber pn = (PhoneNumber) o;
321 | return pn.lineNum == lineNum && pn.prefix == prefix && pn.areaCode == areaCode;
322 | }
323 | // ...
324 | // 其余省略
325 | }
326 | ```
327 |
328 | 这里有几个最后的警告:
329 |
330 | + **覆盖 equals 时总要覆盖 hashCode([第 11 条](item11))。**
331 | + **不要企图让 equals 过于智能。**如果你只是简单地测试了属性是否相等,则不难遵循 equals 约定。如果你在寻找等价性方面过于激进,则很容易陷入麻烦。考虑任何形式的别名通常都是一个坏主意。例如,File 类不应该企图把指向同一个文件的符号链接(symbolic link)当作相等的对象来看待。所幸 File 类没有这样做。
332 | + **不要将 equals 声明中的 Object 对象替换为其他的类型。** 对程序员来说,写出如下的 equals 方法并不罕见,这会使程序员花费数小时困惑于为什么它不能正确地工作:
333 |
334 | ```java
335 | // 参数类型必须是 Object
336 | public boolean equals(MyClass o) {
337 | //...
338 | }
339 | ```
340 |
341 | 这个的问题在于,这个方法没有覆盖参数是 Object 类型的 Object.equals 方法,相反,它重载了这个方法([第 52 条](item52))。提供这样一个 “强类型(strongly typed)” 的 equals 方法是无法接受,即使这是除普通 equals 方法之外的方法,因为它会导致子类中的 Override 注解产生误报以及提供错误的安全感。正如本条目自始自终所描述的,一致地使用 Override 注解将防止你犯这个错误([第 40 条](item40))。这个 equals 方法将无法被编译,并且错误信息将告诉你确切错误是什么:
342 |
343 | ```java
344 | // 无法编译
345 | @Override
346 | public boolean equals(MyClass o) {
347 | // ...
348 | }
349 | ```
350 |
351 | 编写和测试 equals (和 hashCode)方法是乏味的,并且测试结果也是平常的。手动编写和测试这些方法的一个极佳方案就是使用 Google 的开源的 AutoValue 框架,这个框架会自动地为你生成这些方法,只需要通过在类上标注一个简单的注解即可。在多数情况下,AutoValue 生成的方法在本质上与那些你自己编写的方法是相同的。
352 |
353 | IDE 也有生成 equals 和 hashCode 方法的工具, 但是生成的结果代码相比于使用 AutoValue 生成的代码而言更加的冗长且难以阅读,不具备自动跟踪类中的更改,因此需要进行测试。即便如此,让 IDE 生成 equals (和 hashCode)方法通常比手动实现它们更可取,因为 IDE 不会犯粗心的错误,而人类则会犯错。
354 |
355 | 总之,不要覆盖 equals 方法,除非你不得不这么做。在很多情况下,继承自 Object 的实现完全符合你的需求。如果你确实覆盖了 equals 方法,请确保你比较该类的所有重要属性,并且以保留 equals 约定的所有五项规定的方式对它们进行比较。
356 |
357 |
358 |
359 | [Liskov87] Liskov, B. 1988. Data Abstraction and Hierarchy. In Addendum to the Proceedings of OOPSLA ’87 and SIGPLAN Notices, Vol. 23, No. 5: 17–34, May 1988.
360 |
361 | [item1]: ../2.创建和销毁对象/第%201%20条:考虑用静态工厂方法代替构造器.md " 第2章第1条"
362 | [item6]: ../2.创建和销毁对象/第%206%20条:避免创建不必要的对象.md "第2章第6条"
363 | [item11]: url "在未来填入第11条的url,否则无法跳转"
364 | [item17]: url "在未来填入第17条的url,负责无法跳转"
365 | [item18]: url "在未来填入第18条的url,否则无法跳转"
366 | [item23]: url "在未来填入第23条的url,否则无法跳转"
367 | [item34]: url "在未来填入第34条的url,负责无法跳转"
368 | [item40]: url "在未来填入第40条的url,负责无法跳转"
369 | [item52]: url "在未来填入第52条的url,负责无法跳转"
370 |
371 | ---
372 |
373 | > 翻译:Inno
374 | >
375 | > 校对:
--------------------------------------------------------------------------------
/3.对所有对象都通用的方法/第 11 条:覆盖 equals 方法时要覆盖 hashCode 方法.md:
--------------------------------------------------------------------------------
1 | # 第 11 条:覆盖 equals 方法时必须覆盖 hashCode 方法
2 |
3 | **覆盖每个类的 equals() 方法必须覆盖对应的 hashCode() 方法**。如果不这么做,类将会违反 hashCode 的通用约定,从而导致该类无法在基于散列的集合中正常运作,比如 HaspMap 和 HashSet 集合。
4 |
5 | 下面是约定的内容,摘自 Object 规范:
6 |
7 | - 在应用程序执行期间,只要某对象的 equals 方法的比较操作所用到的信息没有修改过,那么多次调用该对象的 hashCode 方法时必须返回同一个值。在同一个应用程序的多次执行过程中,每次执行所返回的值可以不一致。
8 | - 如果两个对象通过 equals( Object ) 方法判定是相等的,那么在两个对象上调用 hashCode 必须产生同样的整数值。
9 | - 如果两个对象通过 equals( Object ) 方法判定是不等的,在每个对象上调用 hashCode 方法可以产生相同的结果。然而,程序员应该知道,让不相等的对象产生不同的结果可以提高散列表的性能。
10 |
11 | **当没有覆盖 hashCode 时会违反关键约定的第二条:相等的对象必须具有相等的 hash 码**。根据类的 equals 方法,两个不同的实例有可能逻辑上是相同的,但根据 Object 类的 hashCode 方法,它们仅仅是两个没有任何共同之处的对象。因此,Object 类的 hashCode 方法返回两个看上去是随机的整数,而不是第二个要求约定的那样,返回两个相等的整数。
12 |
13 | 例如,假设你使用[第十条][Item10]中的 PhoneNumber 类的实例作为 HashMap 的 key 值:
14 |
15 | ```java
16 | Map m = new HashMap<>();
17 | m.put(new PhoneNumber(707, 867, 5309), "Jenny");
18 | ```
19 |
20 | 这种情况下,你可能期望 m.get(new PhoneNumber(707, 867, 5309)) 会返回 “Jenny”,然而事实并非如此,它将会返回 null 。注意,这里涉及了两个 PhoneNumber 实例:一个是插入到 HashMap 中的实例,另一个相等的实例用于(试图用于)检索。PhoneNumber 类没有覆盖 hashCode 方法导致这两个相等的实例拥有不相等的 hash 码,违反了 hashCode 的约定。因此,get 方法有可能从一个不同于 put 方法放入的 hash 桶(hash bucket)中查找这个电话号码。即使这两个实例整好被放到同一个 hash 桶中,get方法也必定会返回 null,因为 HashMap 有一项优化,可以将与每个项相关联的 hash 码缓存起来,如果 hash 码不匹配时,也不必检验对象是否相等。
21 |
22 | 解决这种问题也很简单,只要给 PhoneNumber 写一个适当的 hashCode 方法即可。那么,hashCode 方法应该是怎样的呢?编写一个合法但不好用的 hashCode 方法没有任何价值。比如下面这个,虽然合法但永远都不会被使用:
23 |
24 | ```java
25 | // 最糟糕的 hashCode 方法的合法实现 - 永远不要使用!
26 | @Override public int hashCode() { return 42; }
27 | ```
28 |
29 | 这样写合法是因为它确保了相等的对象拥有同样的 hash 码,可它同时更残暴地让每个对象都拥有同一个 hash 码。因此,每个对象都被映射到同一个 hash 桶中,而且 hash 表也退化成了链表。本应该线性时间运行的程序却以二次时间运行。对规模更大的 hash 表来说,这会导致能运行和不能运行的区别。
30 |
31 | 一个好的 hash 函数应该为不等的实例生成不同的 hash 码,这正是 hashCode 约定的第三条。理想情况下,一个 hasn 函数应该把不相等实例的合理集合均匀分布在所有的 int 值上。然而要达到理想状态是很困难的,幸运的是实现近似公平并不是很难。这里有一些简单的方法:
32 |
33 | 1. 声明一个名为 result 的 int 变量,把对象中第一个有意义的字段初始化为 hash 码 C,如步骤 2 中 a 所计算的。(回想一下[第十条][Item10],关键域是指 equals 方法中涉及的每个域。)
34 |
35 | 2. 对于对象中每个残留关键域 f ,执行下列步骤:
36 |
37 | a. 为该域计算一个 int 型的 hash 码 C:
38 |
39 | i. 如果该域是私有类型,则计算 `Type.hashCode(f)`,其中 Type 是对应于 f 类型的装箱类。
40 |
41 | ii. 如果该域是对象引用,并且该类的 equals 方法递归地调用 equals 方法比较该域,递归调用域内的 hashCode 方法。如果需要更复杂的对比关系,则为这个域计算一个“范式(canonical representation)”,然后针对这个范式调用 hashCode。如果这个域的值是 null,则返回 0(或者其他常数,但通常是 0)。
42 |
43 | iii. 如果该域是数组,则要把每一个元素当作单独的域来处理。也就是说,递归地应用上述规则,对每个关键元素计算一个 hash 码,然后根据步骤 2.b. 中的做法把这些 hash 值组合起来。如果数组中没有关键元素,则使用常量,但最好不要使用 0。如果所有元素都是关键元素,使用 `Arrays.hashCode`。
44 |
45 | b. 按照下面的公式,把步骤 2.a. 计算的 hash 码 C 合并到 result 中:`result = 31 * result + c`
46 |
47 | 3. 返回 result。
48 |
49 | 当写完了 hashCode 方法之后,问问自己是否相等的实例都含有相等的 hash 码,并编写测试单元来确认你的直觉是否正确(除非使用自动赋值(AutoValue)生成 equals 方法和 hashCode 方法,这种情况下可以安全的省略这些测试)。如果相等的实例含有不等的 hash 码,要找出原因并修复问题。
50 |
51 | 在 hash 码计算中,可以把冗余域(redundant field)排除在外。换句话说,如果一个域的值可以根据参与计算的其他域计算出来,则可以把这样的域排除在外。必须排除 equals 比较计算中没有用到的域,否则可能会违反 hashCode 约定中的第二条。
52 |
53 | 步骤 2.b 中的乘法会使 result 依赖域的顺序,如果类中有多个相似的域,则产生一个更好的 hash 函数。比如,如果从 String 类中的 hash 函数省略了乘法运算,所有的字都拥有相同的 hash 码。选择数值 31 是因为它是奇素数。如果它是偶数而且乘法溢出,便会丢失信息,因为乘法就是移位运算。使用素数的优点并不是很明了,但习惯上都是这么用的。31 有个很好的特性,它可以用一次移位运算和一次减法运算代替乘法运算可以得到更好的性能:`31 * i == ( i << 5 ) - i`。现代虚拟机自动完成这种优化。
54 |
55 | 现在我们把上述解决办法用到 PhoneNumber 类中:
56 |
57 | ```java
58 | // 典型的 hashCode 方法
59 | @Override
60 | public int hashCode() {
61 | int result = Short.hashCode(areaCode);
62 | result = 31 * result + Short.hashCode(prefix);
63 | result = 31 * result + Short.hashCode(lineNum);
64 | return result;
65 | }
66 | ```
67 |
68 | 因为这个方法返回的是一个简单、确定的计算结果,它的输入只是 PhoneNumber 实例中的三个关键域,因此相等的 PhoneNumber 显然都会有相等的 hash 码。事实上,这个方法完美地实现了 PhoneNumber 类的 hashCode 方法,相当于 Java 平台类库中的实现。它的作法很简单,相当快捷,而且恰当地把不相等的电话号码分散到不同的 hash 桶中。
69 |
70 | 虽然本条目中的解决方法产生了相当不错的 hash 函数,但它们并不是最优秀的。这些方法在 hash 函数的质量上可以与 Java 平台类库的数据类型的 hash 函数相媲美,对于大多数用途来说都是足够的。如果确实需要 hash 函数来减少冲突,请查看 Guava 的 com.google.common.hash.Hashing [Guava][Guava]。
71 |
72 | Objects 类有一个静态方法,这个静态方法可以存放任意数量的对象并为它们返回一个 hash 码。这个方法名叫 hash,只写一行 hashCode 方法,质量却可以和本条提供的解决方法相媲美。不幸的是,它们运行地更慢,因为它们需要创建数组来传递可变数量的参数,而且如果所有参数均属于原始类型,则可以进行装箱和反装箱。
73 |
74 | 这种风格的 hash 函数只有在性能要求不严格的情况下才推荐使用。下面是使用这种方法写的 PhoneNumber 类的 hash函数:
75 |
76 | ```java
77 | // 一行式 hashCode 方法 - 性能一般
78 | @Override
79 | public int hashCode() {
80 | return Objects.hash(lineNum, prefix, areaCode);
81 | }
82 | ```
83 |
84 | 如果类是不可变的,并且计算 hash 码的开销也比较大,应该考虑把 hash 码缓存在对象内部,而不是每次请求的时候都重新计算 hash 码。如果你觉得这种类型的大多数对象会被当作 hash 键值(hash keys)使用,就应该在创建实例的时候计算 hash 码。否则,可以选择延迟初始化(lazily initialize)hash 码,在第一次调用 hashCode 时初始化。当使用延迟初始化方式时,要保持线程的安全需要注意一些条件([第 83 条][Item83])。PhoneNumber 类不适合这样的处理方式,在这里仅仅展示它是如何实现的。注意 hashCode 域的初始值(在本例中为 0 )不应该是通过常规方式创建的实例的 hash 码。
85 |
86 | ```java
87 | // 使用延迟初始化的 hashCode 方法缓存 hash 码
88 | private int hashCode; // 自动初始为 0
89 | @Override
90 | public int hashCode() {
91 | int result = hashCode;
92 | if (result == 0) {
93 | result = Short.hashCode(areaCode);
94 | result = 31 * result + Short.hashCode(prefix);
95 | result = 31 * result + Short.hashCode(lineNum);
96 | hashCode = result;
97 | }
98 | return result;
99 | }
100 | ```
101 |
102 | **不要试图从 hash 码的计算中排除一些关键域来提高性能**。虽然 hash 函数的结果可以跑得很快,但它的质量太差,可能会降低 hash 表的性能导致其根本无法使用。特别是在实践中,hash 函数要面对大量的实例集合,在你忽略掉的区域中,这些实例仍然区别很大。如果这样的话,hash 函数将会把所有的实例映射到极少数的 hash 码上,本应该以线性时间运行的程序就会以平方级的时间运行。
103 |
104 | 这不仅仅是一个理论问题。在 Java 2 发行之前,String 类的 hash 函数最多只能检查 16 个字符,从第一个字符开始,在整个字符串中均匀选取。对于像 URL 这样的大型的分层名字的集合来说,这样的 hash 函数正好表现出了这里所提到的病态行为。
105 |
106 | **不要给 hashCode 方法返回的值提供明确的规范,这样客户端就不能合理地使用它;也使 hash 函数的改变更复杂了**。在 Java 类库中,比如 String 和 Integer 类,将其 hashCode 方法返回的确切值指定为实例值的函数。这并不是好办法,而是一个我们不得不面对的错误:它限制了在未来版本中提高 hash 函数的能力。如果保留了未指定的细节,并且在 hash 函数中发现了缺陷,或者发现了更好的 hash 函数,可以在后续版本中改进它。
107 |
108 | 总之,每次重写 hashCode 方法时都必须重写 equals 方法,否则程序将不能正确运行。HashCode 方法必须遵守 Object 类中的规定的约定,必须为不同的实例合理分配不等的 hash 码。这很容易实现,如果稍微单调乏味,可以使用第 51 页的方法。就像[第 10 条][Item10]中提到的那样,自动赋值框架为手动编写 equals 和 hashCode 方法提供了一个很好的选择,IDE 同样提供了一些这样的功能。
109 |
110 | > 翻译:Inger
111 | >
112 | > 校对:Angus
113 |
114 |
115 |
116 | [Item10]: 第%2010%20条:覆盖%20equals%20时请遵守通用约定.md "第十条"
117 | [Item83]: url "在未来填入第 83 条的 url,否则无法进行跳转"
118 | [Guava]: https://github.com/google/guava "Guava. 2017. Google Inc."
119 |
120 |
--------------------------------------------------------------------------------
/3.对所有对象都通用的方法/第 12 条:始终要覆盖 toString.md:
--------------------------------------------------------------------------------
1 | # 第 12 条:始终要覆盖 toString
2 |
3 | Object类提供的toString方法返回的字符串通常不是你想看到的。
4 | 它由后跟“@”符号和哈希代码的无符号十六进制表示,例如,PhoneNumber@163b91。
5 | toString源码里讲到“返回的内容应该简洁且一应俱全,这样利于人类阅读。” 可以说PhoneNumber@163b91这种表现方式是简洁并利与人类阅读的,
6 | 与707-867-5309相比,它没有提供太多的信息。源码里还讲到“建议所有子类都重写此方法。” 金玉之言!
7 |
8 | 与遵守equals和hashcode约定一样重要(第10和11条),提供一个好的toString方法实现使你的类能更方便的进行Debug调试。
9 | 这个方法应该当把对象传递给println、printf、字符串连接操作符或断言,或由调试器打印时,自动调用toString方法。
10 | 即使你从来没有通过对象调用过此方法。例如,一个组件引用了你的对象可能也包括你的对象中toString方法。这时如果你不重写toString方法的话,toString方法提示的信息可能无济于事。
11 |
12 | 如果你给PhoneNumber提供一个好的toString方法,生成一个有用的诊断信息是很容易的就像这样:
13 |
14 | ```System.out.println("Failed to connect to " + phoneNumber);```
15 |
16 | 不管是否重写toString,程序员们都将会以这种方式生成诊断信息,但是除非你这样做否则信息不会有什么用。
17 | 提供一个好的toString方法延伸到类的实例之外,包含对这些实例的引用,特别是集合。当打印一个map时你想看以下两种表达形式中的哪个?
18 |
19 | `{Jenny=PhoneNumber@163b91} or {Jenny=707-867-5309}`
20 |
21 | 实际使用中,toString方法应该返回所有的感兴趣的信息包含在这个对象里,如PhoneNumber这个例子所示。如果一个对象特别大或者它包含了一个不利于字符串表示的状态,这样的对象是很不现实的。
22 | 在这种情况下,toString方法应该返回一个摘要就像 **Manhattan residential phone directory**(1487536listings) 或 **Thread[main,5,main]**
23 | 理想上,字符串是不言而明的,未能将对象的所有有趣信息包含在其字符串表现形式中是一个特别烦人的处罚,就像这样:
24 |
25 | `Assertion failure: expected {abc, 123}, but was {abc,123}.`
26 |
27 | 你在实现一个toString方法时不得不做的一个重要的决定就是是否在文档中指定返回值的格式。建议你为值类型这样做,比如电话号码或矩阵。
28 | 指定格式的优势在于它是作为一个标准的、清楚的和人工可读的对象表示。这个表示方法可以用于输入输出和对象持久化人工可读的数据,比如CSV文件。
29 | 如果你指定了格式,通常提供一个匹配的静态工厂或构造函数是一个好主意,如此,程序员们可以很容易的在对象和它的字符串表现形式中来回转换。在Java平台的库里,
30 | 有许多值类采用这个方法,比如BigInteger,BigDecimal和原始装箱类的大多数。
31 |
32 | 指定toString返回值格式的缺点在于,一旦你指定了它,那你就要一辈子坚持它,假设您的类被广泛使用。程序员将编写代码来解析这个表达方法。
33 | 去生成它,和将其嵌入到持久化数据中。如果你在未来的版本中改变了这个表示方法,你会破坏掉他们的代码和数据,然后他们就会把你骂成狗。
34 | 通过选择不指定返回值的格式,你保证了在后续版本中添加信息或改进格式的灵活性。
35 |
36 | **无论你是否指定返回值的格式,你都应该清晰的文档化你的意图。**如果你指定了格式,你恰恰应该这样做。例如,这里是一个toString方法,与条目11的PhoneNumber类相匹配:
37 | ```/** * 返回电话号码的字符串表示。
38 |
39 | * 字符串包含十二个字符,它的格式是"XXX-YYY-ZZZZ",
40 | * 这个格式中,XXX是地区码,YYY 是前缀,ZZZ是线路号码。
41 | * 每个大写字母代表单个十进制数字。
42 | *
43 | * 如果这个电话号码的三个部分的任何一个太小了,以致不能填满它的地方,这个地方开头填充零。
44 | * 比如,如果线路号码的值是123,字符串表示的最后四个字符为0123。
45 | */
46 | @Override public String toString() {
47 | return String.format("%03d-%03d-%04d",
48 | areaCode, prefix, lineNum);
49 | }
50 | ```
51 | 如果你没有指定一个返回类型,注释内容读起来应该像这样:
52 | ```/**
53 | * 返回这个部分的简短描述。表示的具体细节是未指定的而且可以改变,
54 | * 但是如下可能当做典型的:
55 | *
56 | * "[Potion #9: type=love, smell=turpentine, look=india ink]"
57 | */
58 | @Override public String toString() { ... }
59 | ```
60 |
61 | 读完这些内容之后,程序员们在格式更改时,生成依赖于格式细节的代码或持久数据导致发生了一些问题,这只能怪他们自己。
62 |
63 | 无论是否指定了格式,都要提供以编程方式访问toString返回值中包含的信息。比如PhoneNumber类应该包含地区代码,前缀和线路代码的存取器。
64 | 如果你不这样做,你会强迫需要这些信息的程序员去解析这个字符串。此外也会降低性能,为程序员带来不必要的工作。 这个过程很容易出错,而且如果你改变了这个格式,会导致脆弱的系统崩溃。
65 | 未能提供存取器,你把字符串格式改变成一个事实上的API,即使你指定它是可以改变的。
66 |
67 | 在一个静态实用类中编写toString方法是没有什么意义的(条目四)。你也没必要在大多数枚举类型中编写toString方法因为Java提供了很好的一个,然而,你应该在每一个抽象类中编写toString方法,
68 | 这个抽象类的子类共享一个通用的字符串表现方法。比如,在大多数数据集实现中的toString方法是继承于抽象的数据集类。
69 |
70 | 谷歌的开源框架AutoValue,就像在条目10中讨论的,将会为你生成一个toString方法,大多数IDE也会这样做。
71 | 这些方法很好的告诉你每个字段的内容,但是没有明确的告诉你这个类的意图。所以,比如,为我们的PhoneNumber类使用自动生成的toString方法是不适当的
72 | (作为电话好吗应该有一个标准的字符串表示方法),但是这种方法可能对我们的Potion类是可以接受的。
73 | 这就是说,一个自动生成的toString方法远远比继承自Object(没有告诉你关于对象值的任何事情)的可取。
74 |
75 | 总的来说,在每个你编写的不可实例化的类中,都要重写Object类的toString方法,除非一个超类已经这么做了。
76 | 这使得类更加舒适和便与调试。这个toString方法应该返回一个简洁的、有用的对象描述,以一个美观的格式。
--------------------------------------------------------------------------------
/3.对所有对象都通用的方法/第三章 对所有对象都通用的方法.md:
--------------------------------------------------------------------------------
1 | # 第 3 章 对所有对象都通用的方法
2 |
3 | 尽管 Object 是一个具体的类,但设计它主要是为了扩展。它所有的非 final 方法(equals、hashCode、toString、clone 和 finalize)都有着明确的通用约定(general contract),因为它们被设计成可覆盖(override)的。任何一个覆盖了这些方法的类都有责任遵守这些通用约定;如果不这样做,将会妨碍其它依赖于这些约定的类(比如 HashMap 和 HashSet)与该类一起正常运作。
4 |
5 | 本章将告诉你何时以及如何去覆盖这些非 final 的 Object 方法。本章省略了 finalize 方法,因为它已经在[第 8 条][item8]中被讨论过了。而 Comparable.compareTo 虽然不是 Object 方法,但因为它具有相似的特性,所以本章会对它进行讨论。
6 |
7 | [item8]: ../2.创建和销毁对象/第%208%20条:避免使用%20Finalizer%20和%20Cleaner%20机制.md "第 8 条:避免使用 Finalizer 和 Cleaner 机制"
8 |
9 | ---
10 |
11 | > 翻译:Inno
12 | >
13 | > 校对:Angus
14 |
--------------------------------------------------------------------------------
/4.类和接口/第 15 条:最小化类及其成员的可访问性.md:
--------------------------------------------------------------------------------
1 | # 第 15 条:最小化类及其成员的可访问性
2 |
3 | 区分模块设计得是否良好,最重要的因素就是,该模块是否对其他模块隐藏了其内部数据和其他实现细节。拥有良好设计的模块会隐藏所有内部实现细节,明确的将其 API 和具体实现细节分割开来。然后,模块直接只能通过它们的 API 进行通信,一个模块不需要知道其他模块的内部工作细节。这种概念称为信息隐藏(*information hiding*)或封装(*encapsulation*),是软件设计的基础性原则。
4 |
5 | 出于种种原因,信息隐藏非常重要,其中大部分原因是因为它解耦了组成系统的各个组件,允许各组件独立地进行开发、测试、优化、应用、理解和修改。由于各组件可以各自独立地开发,从而加快了系统开发。由于可以更快地理解组件并进行调试或替换,而不必担心损害其他组件,从而减轻了维护的负担。虽然信息隐藏本身不会带来良好性能,但它可以有效地对性能进行有些优化:一旦系统完成,分析并确定是哪个组件导致的性能问题时([第 67 条][Item67]),这些问题组件可以在不影响其他组件的同时进行优化。在开发中,低耦合的组件往往在其他环境中也是可用的,信息隐藏提高了软件的重用性。最后,信息隐藏降低了开发大型系统的风险,因为即使系统开发失败,组件也可以独立地开发成功。
6 |
7 | Java 在信息隐藏方面有很多工具。访问控制(*access control* )机制 [JSL,6.6] 明确规定了类、接口和成员的可访问性(*accessibility*)。实体的访问性是由该实体声明所在的位置,以及该实体声明中所出现的访问修饰符(**private, protected,** 和 **public**)决定的。这些修饰符的恰当使用对信息隐藏非常关键。
8 |
9 | 经验法则非常简单:**将每个类或成员都尽可能的设置为不可访问**。换句话说,使用与你正在编写的软件自身功能一致的尽可能低的访问级别。
10 |
11 | 对顶级(非嵌套的)类和接口而言,它们只有两种可能的访问级别:包级私有的(*package-*
12 |
13 | *private*)和公有的(*public*)。如果使用 public 修饰符声明顶级类或接口,该类或接口就会是公有的;否则其将会默认为包级私有的。如果顶级类或接口可以定义为包级私有的,它就应该定义为包级私有的。通过定义类或接口为包级私有的,可以将其作为实现的一部分,而不是暴露出去的 API,也可以在后续版本中修改、替换或删除这些类和接口,而不必担心损害已有的客户端。但如果你定义类或接口为公有的,那就必须永远保证该类和接口的兼容性。
14 |
15 | 如果只有一个类使用包级私有的顶级类或接口,那么考虑将该顶级类定义为唯一使用它的那个类的私有静态嵌套类([第 24 条][Item24])。这样就将顶级类或接口的可访问性从包中的所有类降低到了唯一使用它的类。但是,相比降低包级私有的顶级类的可访问性,降低不必要公有类的可访问性要重要的多:公有类是包的 API 的一部分,而包级私有的顶级类已经是它的实现细节的一部分了。
16 |
17 | 对成员(域、方法、嵌套类和嵌套接口)而言,有四个可能的访问级别,这里按照可访问性的递增次序列出:
18 |
19 | - **private**——只有在声明该成员的顶级类内部才可以访问这个成员。
20 | - **package-private**——可以在声明该成员的类所在包的任何一个类中方为这个成员。从技术上讲,称这种访问级别为默认访问级别(*default*),如果没有为成员指定任何访问修饰符,就采用这个访问级别。
21 | - **protected**——可以在声明该成员的类的子类(但有一些限制 [ JSL, 6.6.2 ]),及声明该成员的类所在包的任何一个类中访问这个成员。
22 | - **public**——可任意访问这个成员。
23 |
24 | 谨慎设计类的公有 API 后,你应该将所有成员设置为私有的。仅当位于同一包内的其他类确实需要访问成员时,才应该去掉 private 修饰符,让该成员成为包级私有的。如果你发现你经常需要这样做,那么就得重新审查系统的设计,看看另一种分解能否产生更好的相互解耦的类。这就是说,私有的和包级私有的成员都是类实现细节的一部分,通常不会影响该类暴露出去的 API。然而,如果类实现 Serializable 接口([第 86 条][Item86] 和 [第 87 条][Item87]),这些域就可能会被“泄露(leak)”到导出的 API 中。
25 |
26 | 就公有类的成员来说,可访问性从包级私有到保护级别巨幅增加。受保护的成员是类所暴露 API 的一部分,必须永远维护。公有类的受保护成员也代表了该类对于某个实现细节的公开承诺([第 19 条][Item19])。受保护的成员应该尽量少用。
27 |
28 | 有一条关键规则限制了降低方法的可访问性的能力。如果一个方法覆盖了父类方法,那么在子类中该方法的可访问级别不能比父类中严苛 [ JSL, 8.4.8.3]。这样可以确保可使用父类实例的任何地方,都可以使用子类实例(*Liskov substitution principle*, 见[第 15 条][Item15])。如果违反了这条原则,在编译子类时,编译器会产生错误信息。关于这条规则有个特殊情况,如果一个类实现了一个接口,那么接口中所有的类方法在这个类中也都必须声明为公有的。
29 |
30 | 为了更好地测试代码,你可以让你的类、接口或成员变得更容易访问。这样做在一定程度上是可以的。为了测试,将公共类的私有成员改为包级私有是可以接受的,但决不允许可访问性再高任何一点。换句话说,不能为了测试将类、接口或成员定义为包导出的 API 。幸运的是,也没有必要这么做,因为测试可以只运行包的一部分来测试,从而能够访问它的包级私有的元素。
31 |
32 | **公有类的实例域绝不能是公有的**([ 第 16 条][Item16])。如果实例域非 final 型,或该域是一个可变对象的引用,那么一旦将其定义为 public,就无法对存储在这个域中的值进行限制;这意味着也无法强制这个域不可变。同样,当域中的值发生改变时你将无可奈何,所以 **包含公有可变域的类不是线程安全的**。即使域是 final 型而且引用不可变的对象,将其设置为公有的,也失去了切换到字段不存在的新内部数据表示形式的灵活性。
33 |
34 | 同样的建议也适用于静态域,只是有一种例外情况。假设常量构成了类提供的整个抽象中的一部分,通过公有的静态 final 域可以暴露这些常量。按照惯例,这样的域一般都是由大写字母命名的,字母间用下划线隔开([第 68 条][Item68])。很重要的一点是,这些域包含初始值或指向不可变对象的引用。如果 final 域包含可变对象的引用,那么将具有非 final 域的所有缺点。虽然引用本身不能修改,但被引用的对象可以修改,一旦修改,会造成灾难性的后果。
35 |
36 | 注意非空数组一定是可变的,所以**类不可以含有公有的静态 final 数组域,或返回公有的静态 final 数据域的访问方法**。如果类含有这样的域或访问方法,客户端将可以修改数组的内容。这是一个很常见的导致安全漏洞的原因:
37 |
38 | ```JAVA
39 | //潜在的安全漏洞!
40 | public static final Thing[] VALUES = { ... };
41 | ```
42 |
43 | 一些 IDE 会生成返回私有数组域的引用的访问方法,这样也会导致同样的漏洞。有两种方法解决问题,一种是将公有数组修改为私有的,然后添加公有的不可变的 LIst:
44 |
45 | ```java
46 | private static final Thing[] PRIVATE_VALUES = { ... };
47 | public static final List VALUES = Collections,unmodifiableList(Arrays.asList(PRIVATE_VALUES));
48 | ```
49 |
50 | 另一种方法是将数组设置为私有的,然后一个添加返回这个私有数组备份的公有方法。
51 |
52 | ```java
53 | private static final Thing[] PRIVATE_VALUES = { ... };
54 | public static final Thing[] values(){
55 | return PRIVATE_VALUES.clone();
56 | }
57 | ```
58 |
59 | 要在两种解决方案中选择一种,就需要思考客户端可能会产生的结果。哪种返回类型更便利?哪种性能更好呢?
60 |
61 | 在 Java 9 中,有两个额外的隐式访问级别是作为模块系统(*module system*,通常包含在名为 moudle-info.java 的源文件中)的一部分引入的。模块是一组包的集合,就像包是一组类的集合。模块可以通过它的模块声明(*module declaration*)中的导出声明(*export declaration*)导出一些包。模块中未导出包的公有的和受保护的成员在模块外是不可访问的;在模块内部,这些成员的访问性不受导出声明的影响。使用模块系统,可以在模块中的包之间共享类,而不必让它们对整个世界可见。未导出包中公共类的公有的和受保护的成员多出了两种隐含的访问级别,它们分别是普通和保护级别的模仿。这两种程度共享的需求相对较少,通常可以重新安排包中的类来消除这种需求。
62 |
63 | 不同于四种主要的访问级别,这两种基于模块的访问级别主要是咨询性的。如果将模块的 JAR 文件放在应用的类路径下而不是模块路径下,模块内的包将会表现出非模块化行为:无论模块是否导出这个包,包中公有类的所有公有和受保护成员都有正常的可访问性 [Reinhold, 1.2]。有一个地方严格执行新引入的访问级别,那就是 JDK 本身:Java 依赖中所有未导出的包在模块外都不可访问。
64 |
65 | 模块提供的访问保护功能有限,不仅对典型的Java程序员有用,而且基本上是建议性的;为了利用好 它们,必须将包分组到模块中,在模块声明中显式地声明所有依赖项,重新排列源树,并采取特殊操作来适应任何对模块内的非模块化包的访问[Reinhold, 3]。模块是否会广泛应用在 JDK 以外的地方,现在断言还为时尚早。同样,除非特别需要,不然最好避免使用这些工具。
66 |
67 | 总之,应该尽可能将减少程序元素的可访问性(理性的)。在谨慎设计最小公开化的 API 后,应该避免任何游离的类、接口或成员成为 API 的一部分。除了公有静态 final 域这一例外情况,所有公有类都不该含有公有域。并且要确保公有静态 final 域所引用的对象是不可变的。
68 |
69 |
70 |
71 | > 翻译:Inger
72 | >
73 | > 校对:Inno
74 |
75 |
76 |
77 | [Item16]: url "在未来填入第 16 条链接,否则无法跳转。"
78 | [Item17]: url "在未来填入第 17 条链接,否则无法跳转。"
79 | [Item68]: url "在未来填入第 68 条链接,否则无法跳转。"
80 | [Item19]: url "在未来填入第 19 条链接,否则无法跳转。"
81 | [Item67]: url "在未来填入第 67 条链接,否则无法跳转。"
82 | [Item24]: url "在未来填入第 24 条链接,否则无法跳转。"
83 | [Item86]: url "在未来填入第 86 条链接,否则无法跳转。"
84 | [Item87]: url "在未来填入第 87 条链接,否则无法跳转。"
85 |
86 |
--------------------------------------------------------------------------------
/4.类和接口/第 18 条:复合优于继承.md:
--------------------------------------------------------------------------------
1 | # 第 18 条:复合优于继承
2 |
3 | 继承是实现代码复用的一个有效方法,但并不总是实现代码复用的最好方法。继承使用不当,会导致系统非常脆弱。在同一个包的作用域下使用继承是安全的,因为子类和父类的实现都在同一个程序员的控制之下;或者,继承一个设计规范并且有扩展文档的类也是安全的。然而,跨包边界继承普通类是危险的。提醒一下,本书中使用的继承(inheritance)特指实现继承(*implementation inheritance*),一个类继承另一个类的情况。本条中讨论的问题不适用于接口继承(*interface inheritance*),类实现接口或接口继承另一个接口。
4 |
5 | **与调用方法不同,继承会破坏封装**。换句话说,子类依赖于父类的实现细节。父类的实现可能会随着版本的迭代而发生变化,当父类代码改变时,即使子类的代码没有发生变化,子类也可能被破坏而失去其本身的功能。因此,子类必须随着父类的变化而调整,除非父类的作者提前为相应的扩展做了设计并记录在文档里。
6 |
7 | 举个例子,假设我们有一个程序用到了 HashSet。为了调整程序性能,我们需要查询 HashSet 自创建以来添加了多少元素(不要与当前大小混淆,当前大小会随着元素的删除而减少)。为了提供这个功能,我们编写了一个 HashSet 变体来记录元素插入次数,并提供了一个接口供外部访问该累加量。HastSet 类包含两个添加元素的方法:add 和 addAll,因此我们重写了这两个方法:
8 |
9 | ```java
10 | // 错误 - 继承使用不当!
11 | public class InstrumentedHashSet extends HashSet {
12 | // 元素添加次数
13 | private int addCount = 0;
14 |
15 | public InstrumentedHashSet() {
16 | }
17 |
18 | public InstrumentedHashSet(int initCap, float loadFactor) {
19 | super(initCap, loadFactor);
20 | }
21 |
22 | @Override
23 | public boolean add(E e) {
24 | addCount++;
25 | return super.add(e);
26 | }
27 |
28 | @Override
29 | public boolean addAll(Collection extends E> c) {
30 | addCount += c.size();
31 | return super.addAll(c);
32 | }
33 |
34 | public int getAddCount() {
35 | return addCount;
36 | }
37 | }
38 | ```
39 |
40 | 这个类看起来很合理,但并不能实现想要的效果。假设我们创建了一个实例,并使用 addAll 方法添加了 3 个元素。顺便提一次,我们使用 `List.of` 创建了一个列表,该方法是在 Java 9 中添加的;如果使用的是早期版本,请改用 `Arrays.asList` :
41 |
42 | ```java
43 | InstrumentedHashSet s = new InstrumentedHashSet<>();
44 | s.add(List.of("Snap", "Crackle", "Pop"));
45 | ```
46 |
47 | 我们期待的效果是 `getAddCount` 方法返回 3,但它实际上会返回 6。哪里出错了呢?实际上,HashSet 内部 addAll 方法是在其 add 方法之上实现的,这种实现方法相当合理,尽管 HashSet 文档上没有记录这个实现细节。InstrumentedHashSet 的 addAll 方法执行了 addCount 值加 3,然后通过 `super.addAll` 调用了父类 HashSet 的 addAll 方法。这反过来又为每个元素调用一次在 `InstrumentedHashSet` 中被覆盖的 `add` 方法。这三次调用中的每一次都执行了 addCount 值加一,addCount 总共加 6:使用 addAll 方法添加的每个元素都加了两遍 addCount 的值。
48 |
49 | 我们可以通过去除`InstrumentedHashSet`子类中重写的 addAll 方法来解决这个问题。虽然结果正确,但它的正确性依然取决于 HashSet 中的 addAll 方法是在其 add 方法之上实现的这一事实。这种”自用(self-use)“是实现细节,不能保证在 Java 平台的所有实现中都适用,并且会随着版本的变化而变化。因此,将会导致`InstrumentedHashSet`类的脆弱性。
50 |
51 | 重写 addAll 方法来迭代指定的集合会稍微好一些,为每个元素调用一次 add 方法。这将保证正确的结果,无论 HashSet 的 addAll 方法是否在其 add 方法之上实现,因为 HashSet 的 addAll 实现将不再被调用。然而,这种方法并不能解决所有的问题。它相当于重新实现父类方法,无论其会不会导致自用,这是困难的、耗时的、容易出错的,并且可能会降低性能。此外,由于有些方法必须访问子类的私有字段,重写父类方法并不总是可行的。
52 |
53 | 子类脆弱性的一个相关原因是它们的父类可以在后续版本中获取新方法。假设一个程序的安全性取决于插入到某个集合中的所有元素都满足某个谓词这一事实。这可以通过对集合进行子类化并覆盖能够添加元素的每个方法来保证,以确保在添加元素之前满足谓词。这可以正常工作,直到在后续版本中将能够插入元素的新方法添加到父类中。一旦发生这种情况,就可以仅通过调用新方法来添加“非法”元素,该方法在子类中没有被覆盖。这不是一个纯粹的理论问题。当改造 Hashtable 和 Vector 加入到集合框架时,必须修复几个这种性质的安全漏洞。
54 |
55 | 这些问题都是由重写方法导致的。如果只是添加新方法并避免覆盖现有方法,您可能会认为扩展类是安全的。虽然这种扩展更安全,但也并非没有风险。如果弗雷在后续版本中添加了一个新方法,并且这个新方法的名称和子类中已存在的方法名一致,但返回值不一致,那么将会不再编译子类[JLS, 8.4, 8.3]。如果子类方法与父类方法具有相同方法名和返回类型,那么子类方法会覆盖父类方法,因此,又会遇到上述问题。此外,新的父类方法能否像你期望的那样实现也不确定,因为在编写子类方法时,并没有考虑到同名父类方法的实现。
56 |
57 | 幸运的是,有一种方法可以避免上述提到的所有问题。与其扩展现有类,不如为新类提供一个引用现有类实例的私有字段。这种方法称之为组合(composition),因为现有类成为了新类的一部分。新类中的每个实例方法调用现有类实例中的相应方法并返回结果。这种结果称之为转发(forwarding),并且新类中的方法称之为转发方法(forwarding method)。这样生成的新类将坚如磐石,不依赖于现有类的实现细节。即使像现有类添加新方法也不会对新类产生影响。为了具体说明,下面是适用组合和转发方式实现的 InstrumentedHashSet 。这种实现分为两部分,一部分是类本身,另一部分是一个可重用的、仅包含所有转发方法的转发类:
58 |
59 | ```java
60 | // 封装类 - 使用组合替代继承
61 | public class InstrumentedSet extends ForwardingSet {
62 | private int addCount = 0;
63 |
64 | public InstrumentedSet(Set s) {
65 | super(s);
66 | }
67 |
68 | @Override
69 | public boolean add(E e) { addCount++;
70 | return super.add(e);
71 | }
72 | @Override
73 | public boolean addAll(Collection extends E> c) {
74 | addCount += c.size();
75 | return super.addAll(c);
76 | }
77 | public int getAddCount() {
78 | return addCount;
79 | }
80 | }
81 |
82 | // 可重用的转发类
83 | public class ForwardingSet implements Set {
84 | private final Set s;
85 | public ForwardingSet(Set s) {
86 | this.s = s;
87 | }
88 |
89 | public void clear() { s.clear(); }
90 | public boolean contains(Object o) {
91 | return s.contains(o);
92 | }
93 | public boolean isEmpty() { return s.isEmpty(); }
94 | public int size() { return s.size(); }
95 | public Iterator iterator() { return s.iterator(); }
96 | public boolean add(E e) { return s.add(e); }
97 | public boolean remove(Object o) { return s.remove(o); }
98 | public boolean containsAll(Collection> c) { return s.containsAll(c); }
99 | public boolean addAll(Collection extends E> c) { return s.addAll(c); }
100 | public boolean removeAll(Collection> c) { return s.removeAll(c); }
101 | public boolean retainAll(Collection> c){ return s.retainAll(c); }
102 | public Object[] toArray() { return s.toArray(); }
103 | public T[] toArray(T[] a) { return s.toArray(a); }
104 | @Override
105 | public boolean equals(Object o){ return s.equals(o); }
106 | @Override
107 | public int hashCode() { return s.hashCode(); }
108 | @Override
109 | public String toString() { return s.toString(); }
110 | }
111 | ```
112 |
113 | InstrumentedSet 类的设计是基于已存在的 Set 接口来实现的,该接口捕获了 HashSet 类的功能。除了坚固之外,这种设计还非常灵活。 InstrumentedSet 类实现了 Set 接口,并有一个构造函数,其参数也是 Set 类型。本质上,该类将一个 Set 转换为另一个,添加了检测功能。与基于继承的方法不同,后者仅适用于单个具体类并且需要为超类中每个受支持的构造函数提供单独的构造函数,包装类可用于检测任何 Set 实现,并将与任何预先存在的构造函数一起工作:
114 |
115 | ```java
116 | Set times = new InstrumentedSet<>(new TreeSet<>(cmp));
117 | Set s = new InstrumentedSet<>(new HashSet<>(INIT_CAPACITY));
118 | ```
119 |
120 | InstrumentedSet 类甚至可以用来临时检测一个已经在没有检测的情况下使用的集合实例:
121 |
122 | ```java
123 | static void walk(Set dogs) {
124 | InstrumentedSet iDogs = new InstrumentedSet<>(dogs);
125 | ... // 在该方法里使用 iDogs 而不是 dogs
126 | }
127 | ```
128 |
129 | InstrumentedSet 类被称为包装类,因为每个 InstrumentedSet 实例都包含(“包装”)另一个 Set 实例。这也称为装饰器模式 [Gamma95],因为 InstrumentedSet 类通过添加检测方法来“装饰”一个集合。有时组合和转发的组合被松散地称为委托。从技术上讲,它不是委托,除非包装对象将自己传递给被包装对象 [Lieberman86; Gamma95]。
130 |
131 | 包装类的缺点很少。一个警告是包装类不适合在回调(*callbacks*)框架中使用,其中对象将自引用传递给其他对象以进行后续调用(“回调”)。因为一个被包装的对象不知道它的包装器,它传递一个对自身的引用(this)并且回调避开了包装器。这被称为 SELF 问题 [Lieberman86]。有些人担心转发方法调用的性能影响或包装对象的内存占用影响。事实证明,两者在实践中都没有太大影响。编写转发方法很繁琐,但每个接口只需编写一次可重用的转发类,而且提供了一个转发类。例如,Guava 为所有集合接口 [Guava] 提供转发类。
132 |
133 | 继承仅适用于子类确实是父类的子类型的情况。换句话说,只有当两个类之间存在“is-a”关系时,类 B 才应该扩展类 A。如果你想让 B 类扩展 A 类,问自己一个问题:每个 B 真的都是 A 吗?如果您不能对这个问题如实回答是,B 不应该扩展 A。如果答案是否定的,通常情况下 B 应该包含 A 的私有实例并公开不同的 API:A 不是 B 的必要部分,只是其实现的一个细节。
134 |
135 | Java 平台库中有许多明显违反此原则的地方。例如,堆栈不是 vector,因此 Stack 不应继承 Vector。同样,属性列表不是哈希表,因此 Properties 不应继承 Hashtable。在这两种情况下,组合可能更好。
136 |
137 | 如果在适合组合的情况下使用继承,则没必要暴露实现细节。生成的 API 将与原始实现联系在一起,永远限制类的性能。更严重的是,通过公开内部结构,可以让客户直接访问它们。至少,它会导致语义混乱。例如,如果 p 引用一个 Properties 实例,那么 p.getProperty(key) 可能会产生与 p.get(key) 不同的结果:前一种方法会考虑默认值,而后一种方法是从 Hashtable 继承的,不会考虑默认值。最严重的是,客户端可以通过直接修改父类来破坏子类的不变量。在 Properties 中,设计者打算只允许字符串作为键和值,但是直接访问底层的 Hashtable 允许违反这种不变量。一旦违反,就不能再使用 Properties API 的其他部分(load 和 Store)。到发现这个问题时,再纠正为时已晚,因为客户端依赖于使用非字符串键和值。
138 |
139 | 在决定使用继承代替组合之前,你应该问自己最后一组问题。你打算继承的类在其 API 中是否有任何缺陷?如果是这样,你是否愿意将这些缺陷传播到你的子类 API 中?继承会传播父类 API 中的任何缺陷,而组合可以让你设计一个隐藏这些缺陷的新 API。
140 |
141 | 总而言之,继承很强大,但它是有问题的,因为它违反了封装。仅当子类和父类之间存在真正的子类型关系时才适用。即使这样,如果子类与父类在不同的包中并且父类不是为继承而设计的,继承可能会导致脆弱性。为避免这种脆弱性,请使用组合和转发而不是继承,尤其是在存在实现包装类的适当接口时。包装类不仅比子类更健壮,而且更强大。
142 |
143 |
--------------------------------------------------------------------------------
/4.类和接口/第四章 类和接口.md:
--------------------------------------------------------------------------------
1 | # 第四章 类和接口
2 |
3 | 类和接口是 Java 这门编程语言的核心要素,它们是基本抽象单元。这门编程语言提供了很多强大的基本元素,你可以使用这些元素来设计类和接口。本章节会为你利用这些元素编写可用、强壮和灵活的类和接口提供指南。
4 |
5 | > 翻译:Inger
6 | >
7 | > 校对:Inno
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 | Preamble
9 |
10 | The GNU General Public License is a free, copyleft license for
11 | software and other kinds of works.
12 |
13 | The licenses for most software and other practical works are designed
14 | to take away your freedom to share and change the works. By contrast,
15 | the GNU General Public License is intended to guarantee your freedom to
16 | share and change all versions of a program--to make sure it remains free
17 | software for all its users. We, the Free Software Foundation, use the
18 | GNU General Public License for most of our software; it applies also to
19 | any other work released this way by its authors. You can apply it to
20 | your programs, too.
21 |
22 | When we speak of free software, we are referring to freedom, not
23 | price. Our General Public Licenses are designed to make sure that you
24 | have the freedom to distribute copies of free software (and charge for
25 | them if you wish), that you receive source code or can get it if you
26 | want it, that you can change the software or use pieces of it in new
27 | free programs, and that you know you can do these things.
28 |
29 | To protect your rights, we need to prevent others from denying you
30 | these rights or asking you to surrender the rights. Therefore, you have
31 | certain responsibilities if you distribute copies of the software, or if
32 | you modify it: responsibilities to respect the freedom of others.
33 |
34 | For example, if you distribute copies of such a program, whether
35 | gratis or for a fee, you must pass on to the recipients the same
36 | freedoms that you received. You must make sure that they, too, receive
37 | or can get the source code. And you must show them these terms so they
38 | know their rights.
39 |
40 | Developers that use the GNU GPL protect your rights with two steps:
41 | (1) assert copyright on the software, and (2) offer you this License
42 | giving you legal permission to copy, distribute and/or modify it.
43 |
44 | For the developers' and authors' protection, the GPL clearly explains
45 | that there is no warranty for this free software. For both users' and
46 | authors' sake, the GPL requires that modified versions be marked as
47 | changed, so that their problems will not be attributed erroneously to
48 | authors of previous versions.
49 |
50 | Some devices are designed to deny users access to install or run
51 | modified versions of the software inside them, although the manufacturer
52 | can do so. This is fundamentally incompatible with the aim of
53 | protecting users' freedom to change the software. The systematic
54 | pattern of such abuse occurs in the area of products for individuals to
55 | use, which is precisely where it is most unacceptable. Therefore, we
56 | have designed this version of the GPL to prohibit the practice for those
57 | products. If such problems arise substantially in other domains, we
58 | stand ready to extend this provision to those domains in future versions
59 | of the GPL, as needed to protect the freedom of users.
60 |
61 | Finally, every program is threatened constantly by software patents.
62 | States should not allow patents to restrict development and use of
63 | software on general-purpose computers, but in those that do, we wish to
64 | avoid the special danger that patents applied to a free program could
65 | make it effectively proprietary. To prevent this, the GPL assures that
66 | patents cannot be used to render the program non-free.
67 |
68 | The precise terms and conditions for copying, distribution and
69 | modification follow.
70 |
71 | TERMS AND CONDITIONS
72 |
73 | 0. Definitions.
74 |
75 | "This License" refers to version 3 of the GNU General Public License.
76 |
77 | "Copyright" also means copyright-like laws that apply to other kinds of
78 | works, such as semiconductor masks.
79 |
80 | "The Program" refers to any copyrightable work licensed under this
81 | License. Each licensee is addressed as "you". "Licensees" and
82 | "recipients" may be individuals or organizations.
83 |
84 | To "modify" a work means to copy from or adapt all or part of the work
85 | in a fashion requiring copyright permission, other than the making of an
86 | exact copy. The resulting work is called a "modified version" of the
87 | earlier work or a work "based on" the earlier work.
88 |
89 | A "covered work" means either the unmodified Program or a work based
90 | on the Program.
91 |
92 | To "propagate" a work means to do anything with it that, without
93 | permission, would make you directly or secondarily liable for
94 | infringement under applicable copyright law, except executing it on a
95 | computer or modifying a private copy. Propagation includes copying,
96 | distribution (with or without modification), making available to the
97 | public, and in some countries other activities as well.
98 |
99 | To "convey" a work means any kind of propagation that enables other
100 | parties to make or receive copies. Mere interaction with a user through
101 | a computer network, with no transfer of a copy, is not conveying.
102 |
103 | An interactive user interface displays "Appropriate Legal Notices"
104 | to the extent that it includes a convenient and prominently visible
105 | feature that (1) displays an appropriate copyright notice, and (2)
106 | tells the user that there is no warranty for the work (except to the
107 | extent that warranties are provided), that licensees may convey the
108 | work under this License, and how to view a copy of this License. If
109 | the interface presents a list of user commands or options, such as a
110 | menu, a prominent item in the list meets this criterion.
111 |
112 | 1. Source Code.
113 |
114 | The "source code" for a work means the preferred form of the work
115 | for making modifications to it. "Object code" means any non-source
116 | form of a work.
117 |
118 | A "Standard Interface" means an interface that either is an official
119 | standard defined by a recognized standards body, or, in the case of
120 | interfaces specified for a particular programming language, one that
121 | is widely used among developers working in that language.
122 |
123 | The "System Libraries" of an executable work include anything, other
124 | than the work as a whole, that (a) is included in the normal form of
125 | packaging a Major Component, but which is not part of that Major
126 | Component, and (b) serves only to enable use of the work with that
127 | Major Component, or to implement a Standard Interface for which an
128 | implementation is available to the public in source code form. A
129 | "Major Component", in this context, means a major essential component
130 | (kernel, window system, and so on) of the specific operating system
131 | (if any) on which the executable work runs, or a compiler used to
132 | produce the work, or an object code interpreter used to run it.
133 |
134 | The "Corresponding Source" for a work in object code form means all
135 | the source code needed to generate, install, and (for an executable
136 | work) run the object code and to modify the work, including scripts to
137 | control those activities. However, it does not include the work's
138 | System Libraries, or general-purpose tools or generally available free
139 | programs which are used unmodified in performing those activities but
140 | which are not part of the work. For example, Corresponding Source
141 | includes interface definition files associated with source files for
142 | the work, and the source code for shared libraries and dynamically
143 | linked subprograms that the work is specifically designed to require,
144 | such as by intimate data communication or control flow between those
145 | subprograms and other parts of the work.
146 |
147 | The Corresponding Source need not include anything that users
148 | can regenerate automatically from other parts of the Corresponding
149 | Source.
150 |
151 | The Corresponding Source for a work in source code form is that
152 | same work.
153 |
154 | 2. Basic Permissions.
155 |
156 | All rights granted under this License are granted for the term of
157 | copyright on the Program, and are irrevocable provided the stated
158 | conditions are met. This License explicitly affirms your unlimited
159 | permission to run the unmodified Program. The output from running a
160 | covered work is covered by this License only if the output, given its
161 | content, constitutes a covered work. This License acknowledges your
162 | rights of fair use or other equivalent, as provided by copyright law.
163 |
164 | You may make, run and propagate covered works that you do not
165 | convey, without conditions so long as your license otherwise remains
166 | in force. You may convey covered works to others for the sole purpose
167 | of having them make modifications exclusively for you, or provide you
168 | with facilities for running those works, provided that you comply with
169 | the terms of this License in conveying all material for which you do
170 | not control copyright. Those thus making or running the covered works
171 | for you must do so exclusively on your behalf, under your direction
172 | and control, on terms that prohibit them from making any copies of
173 | your copyrighted material outside their relationship with you.
174 |
175 | Conveying under any other circumstances is permitted solely under
176 | the conditions stated below. Sublicensing is not allowed; section 10
177 | makes it unnecessary.
178 |
179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
180 |
181 | No covered work shall be deemed part of an effective technological
182 | measure under any applicable law fulfilling obligations under article
183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or
184 | similar laws prohibiting or restricting circumvention of such
185 | measures.
186 |
187 | When you convey a covered work, you waive any legal power to forbid
188 | circumvention of technological measures to the extent such circumvention
189 | is effected by exercising rights under this License with respect to
190 | the covered work, and you disclaim any intention to limit operation or
191 | modification of the work as a means of enforcing, against the work's
192 | users, your or third parties' legal rights to forbid circumvention of
193 | technological measures.
194 |
195 | 4. Conveying Verbatim Copies.
196 |
197 | You may convey verbatim copies of the Program's source code as you
198 | receive it, in any medium, provided that you conspicuously and
199 | appropriately publish on each copy an appropriate copyright notice;
200 | keep intact all notices stating that this License and any
201 | non-permissive terms added in accord with section 7 apply to the code;
202 | keep intact all notices of the absence of any warranty; and give all
203 | recipients a copy of this License along with the Program.
204 |
205 | You may charge any price or no price for each copy that you convey,
206 | and you may offer support or warranty protection for a fee.
207 |
208 | 5. Conveying Modified Source Versions.
209 |
210 | You may convey a work based on the Program, or the modifications to
211 | produce it from the Program, in the form of source code under the
212 | terms of section 4, provided that you also meet all of these conditions:
213 |
214 | a) The work must carry prominent notices stating that you modified
215 | it, and giving a relevant date.
216 |
217 | b) The work must carry prominent notices stating that it is
218 | released under this License and any conditions added under section
219 | 7. This requirement modifies the requirement in section 4 to
220 | "keep intact all notices".
221 |
222 | c) You must license the entire work, as a whole, under this
223 | License to anyone who comes into possession of a copy. This
224 | License will therefore apply, along with any applicable section 7
225 | additional terms, to the whole of the work, and all its parts,
226 | regardless of how they are packaged. This License gives no
227 | permission to license the work in any other way, but it does not
228 | invalidate such permission if you have separately received it.
229 |
230 | d) If the work has interactive user interfaces, each must display
231 | Appropriate Legal Notices; however, if the Program has interactive
232 | interfaces that do not display Appropriate Legal Notices, your
233 | work need not make them do so.
234 |
235 | A compilation of a covered work with other separate and independent
236 | works, which are not by their nature extensions of the covered work,
237 | and which are not combined with it such as to form a larger program,
238 | in or on a volume of a storage or distribution medium, is called an
239 | "aggregate" if the compilation and its resulting copyright are not
240 | used to limit the access or legal rights of the compilation's users
241 | beyond what the individual works permit. Inclusion of a covered work
242 | in an aggregate does not cause this License to apply to the other
243 | parts of the aggregate.
244 |
245 | 6. Conveying Non-Source Forms.
246 |
247 | You may convey a covered work in object code form under the terms
248 | of sections 4 and 5, provided that you also convey the
249 | machine-readable Corresponding Source under the terms of this License,
250 | in one of these ways:
251 |
252 | a) Convey the object code in, or embodied in, a physical product
253 | (including a physical distribution medium), accompanied by the
254 | Corresponding Source fixed on a durable physical medium
255 | customarily used for software interchange.
256 |
257 | b) Convey the object code in, or embodied in, a physical product
258 | (including a physical distribution medium), accompanied by a
259 | written offer, valid for at least three years and valid for as
260 | long as you offer spare parts or customer support for that product
261 | model, to give anyone who possesses the object code either (1) a
262 | copy of the Corresponding Source for all the software in the
263 | product that is covered by this License, on a durable physical
264 | medium customarily used for software interchange, for a price no
265 | more than your reasonable cost of physically performing this
266 | conveying of source, or (2) access to copy the
267 | Corresponding Source from a network server at no charge.
268 |
269 | c) Convey individual copies of the object code with a copy of the
270 | written offer to provide the Corresponding Source. This
271 | alternative is allowed only occasionally and noncommercially, and
272 | only if you received the object code with such an offer, in accord
273 | with subsection 6b.
274 |
275 | d) Convey the object code by offering access from a designated
276 | place (gratis or for a charge), and offer equivalent access to the
277 | Corresponding Source in the same way through the same place at no
278 | further charge. You need not require recipients to copy the
279 | Corresponding Source along with the object code. If the place to
280 | copy the object code is a network server, the Corresponding Source
281 | may be on a different server (operated by you or a third party)
282 | that supports equivalent copying facilities, provided you maintain
283 | clear directions next to the object code saying where to find the
284 | Corresponding Source. Regardless of what server hosts the
285 | Corresponding Source, you remain obligated to ensure that it is
286 | available for as long as needed to satisfy these requirements.
287 |
288 | e) Convey the object code using peer-to-peer transmission, provided
289 | you inform other peers where the object code and Corresponding
290 | Source of the work are being offered to the general public at no
291 | charge under subsection 6d.
292 |
293 | A separable portion of the object code, whose source code is excluded
294 | from the Corresponding Source as a System Library, need not be
295 | included in conveying the object code work.
296 |
297 | A "User Product" is either (1) a "consumer product", which means any
298 | tangible personal property which is normally used for personal, family,
299 | or household purposes, or (2) anything designed or sold for incorporation
300 | into a dwelling. In determining whether a product is a consumer product,
301 | doubtful cases shall be resolved in favor of coverage. For a particular
302 | product received by a particular user, "normally used" refers to a
303 | typical or common use of that class of product, regardless of the status
304 | of the particular user or of the way in which the particular user
305 | actually uses, or expects or is expected to use, the product. A product
306 | is a consumer product regardless of whether the product has substantial
307 | commercial, industrial or non-consumer uses, unless such uses represent
308 | the only significant mode of use of the product.
309 |
310 | "Installation Information" for a User Product means any methods,
311 | procedures, authorization keys, or other information required to install
312 | and execute modified versions of a covered work in that User Product from
313 | a modified version of its Corresponding Source. The information must
314 | suffice to ensure that the continued functioning of the modified object
315 | code is in no case prevented or interfered with solely because
316 | modification has been made.
317 |
318 | If you convey an object code work under this section in, or with, or
319 | specifically for use in, a User Product, and the conveying occurs as
320 | part of a transaction in which the right of possession and use of the
321 | User Product is transferred to the recipient in perpetuity or for a
322 | fixed term (regardless of how the transaction is characterized), the
323 | Corresponding Source conveyed under this section must be accompanied
324 | by the Installation Information. But this requirement does not apply
325 | if neither you nor any third party retains the ability to install
326 | modified object code on the User Product (for example, the work has
327 | been installed in ROM).
328 |
329 | The requirement to provide Installation Information does not include a
330 | requirement to continue to provide support service, warranty, or updates
331 | for a work that has been modified or installed by the recipient, or for
332 | the User Product in which it has been modified or installed. Access to a
333 | network may be denied when the modification itself materially and
334 | adversely affects the operation of the network or violates the rules and
335 | protocols for communication across the network.
336 |
337 | Corresponding Source conveyed, and Installation Information provided,
338 | in accord with this section must be in a format that is publicly
339 | documented (and with an implementation available to the public in
340 | source code form), and must require no special password or key for
341 | unpacking, reading or copying.
342 |
343 | 7. Additional Terms.
344 |
345 | "Additional permissions" are terms that supplement the terms of this
346 | License by making exceptions from one or more of its conditions.
347 | Additional permissions that are applicable to the entire Program shall
348 | be treated as though they were included in this License, to the extent
349 | that they are valid under applicable law. If additional permissions
350 | apply only to part of the Program, that part may be used separately
351 | under those permissions, but the entire Program remains governed by
352 | this License without regard to the additional permissions.
353 |
354 | When you convey a copy of a covered work, you may at your option
355 | remove any additional permissions from that copy, or from any part of
356 | it. (Additional permissions may be written to require their own
357 | removal in certain cases when you modify the work.) You may place
358 | additional permissions on material, added by you to a covered work,
359 | for which you have or can give appropriate copyright permission.
360 |
361 | Notwithstanding any other provision of this License, for material you
362 | add to a covered work, you may (if authorized by the copyright holders of
363 | that material) supplement the terms of this License with terms:
364 |
365 | a) Disclaiming warranty or limiting liability differently from the
366 | terms of sections 15 and 16 of this License; or
367 |
368 | b) Requiring preservation of specified reasonable legal notices or
369 | author attributions in that material or in the Appropriate Legal
370 | Notices displayed by works containing it; or
371 |
372 | c) Prohibiting misrepresentation of the origin of that material, or
373 | requiring that modified versions of such material be marked in
374 | reasonable ways as different from the original version; or
375 |
376 | d) Limiting the use for publicity purposes of names of licensors or
377 | authors of the material; or
378 |
379 | e) Declining to grant rights under trademark law for use of some
380 | trade names, trademarks, or service marks; or
381 |
382 | f) Requiring indemnification of licensors and authors of that
383 | material by anyone who conveys the material (or modified versions of
384 | it) with contractual assumptions of liability to the recipient, for
385 | any liability that these contractual assumptions directly impose on
386 | those licensors and authors.
387 |
388 | All other non-permissive additional terms are considered "further
389 | restrictions" within the meaning of section 10. If the Program as you
390 | received it, or any part of it, contains a notice stating that it is
391 | governed by this License along with a term that is a further
392 | restriction, you may remove that term. If a license document contains
393 | a further restriction but permits relicensing or conveying under this
394 | License, you may add to a covered work material governed by the terms
395 | of that license document, provided that the further restriction does
396 | not survive such relicensing or conveying.
397 |
398 | If you add terms to a covered work in accord with this section, you
399 | must place, in the relevant source files, a statement of the
400 | additional terms that apply to those files, or a notice indicating
401 | where to find the applicable terms.
402 |
403 | Additional terms, permissive or non-permissive, may be stated in the
404 | form of a separately written license, or stated as exceptions;
405 | the above requirements apply either way.
406 |
407 | 8. Termination.
408 |
409 | You may not propagate or modify a covered work except as expressly
410 | provided under this License. Any attempt otherwise to propagate or
411 | modify it is void, and will automatically terminate your rights under
412 | this License (including any patent licenses granted under the third
413 | paragraph of section 11).
414 |
415 | However, if you cease all violation of this License, then your
416 | license from a particular copyright holder is reinstated (a)
417 | provisionally, unless and until the copyright holder explicitly and
418 | finally terminates your license, and (b) permanently, if the copyright
419 | holder fails to notify you of the violation by some reasonable means
420 | prior to 60 days after the cessation.
421 |
422 | Moreover, your license from a particular copyright holder is
423 | reinstated permanently if the copyright holder notifies you of the
424 | violation by some reasonable means, this is the first time you have
425 | received notice of violation of this License (for any work) from that
426 | copyright holder, and you cure the violation prior to 30 days after
427 | your receipt of the notice.
428 |
429 | Termination of your rights under this section does not terminate the
430 | licenses of parties who have received copies or rights from you under
431 | this License. If your rights have been terminated and not permanently
432 | reinstated, you do not qualify to receive new licenses for the same
433 | material under section 10.
434 |
435 | 9. Acceptance Not Required for Having Copies.
436 |
437 | You are not required to accept this License in order to receive or
438 | run a copy of the Program. Ancillary propagation of a covered work
439 | occurring solely as a consequence of using peer-to-peer transmission
440 | to receive a copy likewise does not require acceptance. However,
441 | nothing other than this License grants you permission to propagate or
442 | modify any covered work. These actions infringe copyright if you do
443 | not accept this License. Therefore, by modifying or propagating a
444 | covered work, you indicate your acceptance of this License to do so.
445 |
446 | 10. Automatic Licensing of Downstream Recipients.
447 |
448 | Each time you convey a covered work, the recipient automatically
449 | receives a license from the original licensors, to run, modify and
450 | propagate that work, subject to this License. You are not responsible
451 | for enforcing compliance by third parties with this License.
452 |
453 | An "entity transaction" is a transaction transferring control of an
454 | organization, or substantially all assets of one, or subdividing an
455 | organization, or merging organizations. If propagation of a covered
456 | work results from an entity transaction, each party to that
457 | transaction who receives a copy of the work also receives whatever
458 | licenses to the work the party's predecessor in interest had or could
459 | give under the previous paragraph, plus a right to possession of the
460 | Corresponding Source of the work from the predecessor in interest, if
461 | the predecessor has it or can get it with reasonable efforts.
462 |
463 | You may not impose any further restrictions on the exercise of the
464 | rights granted or affirmed under this License. For example, you may
465 | not impose a license fee, royalty, or other charge for exercise of
466 | rights granted under this License, and you may not initiate litigation
467 | (including a cross-claim or counterclaim in a lawsuit) alleging that
468 | any patent claim is infringed by making, using, selling, offering for
469 | sale, or importing the Program or any portion of it.
470 |
471 | 11. Patents.
472 |
473 | A "contributor" is a copyright holder who authorizes use under this
474 | License of the Program or a work on which the Program is based. The
475 | work thus licensed is called the contributor's "contributor version".
476 |
477 | A contributor's "essential patent claims" are all patent claims
478 | owned or controlled by the contributor, whether already acquired or
479 | hereafter acquired, that would be infringed by some manner, permitted
480 | by this License, of making, using, or selling its contributor version,
481 | but do not include claims that would be infringed only as a
482 | consequence of further modification of the contributor version. For
483 | purposes of this definition, "control" includes the right to grant
484 | patent sublicenses in a manner consistent with the requirements of
485 | this License.
486 |
487 | Each contributor grants you a non-exclusive, worldwide, royalty-free
488 | patent license under the contributor's essential patent claims, to
489 | make, use, sell, offer for sale, import and otherwise run, modify and
490 | propagate the contents of its contributor version.
491 |
492 | In the following three paragraphs, a "patent license" is any express
493 | agreement or commitment, however denominated, not to enforce a patent
494 | (such as an express permission to practice a patent or covenant not to
495 | sue for patent infringement). To "grant" such a patent license to a
496 | party means to make such an agreement or commitment not to enforce a
497 | patent against the party.
498 |
499 | If you convey a covered work, knowingly relying on a patent license,
500 | and the Corresponding Source of the work is not available for anyone
501 | to copy, free of charge and under the terms of this License, through a
502 | publicly available network server or other readily accessible means,
503 | then you must either (1) cause the Corresponding Source to be so
504 | available, or (2) arrange to deprive yourself of the benefit of the
505 | patent license for this particular work, or (3) arrange, in a manner
506 | consistent with the requirements of this License, to extend the patent
507 | license to downstream recipients. "Knowingly relying" means you have
508 | actual knowledge that, but for the patent license, your conveying the
509 | covered work in a country, or your recipient's use of the covered work
510 | in a country, would infringe one or more identifiable patents in that
511 | country that you have reason to believe are valid.
512 |
513 | If, pursuant to or in connection with a single transaction or
514 | arrangement, you convey, or propagate by procuring conveyance of, a
515 | covered work, and grant a patent license to some of the parties
516 | receiving the covered work authorizing them to use, propagate, modify
517 | or convey a specific copy of the covered work, then the patent license
518 | you grant is automatically extended to all recipients of the covered
519 | work and works based on it.
520 |
521 | A patent license is "discriminatory" if it does not include within
522 | the scope of its coverage, prohibits the exercise of, or is
523 | conditioned on the non-exercise of one or more of the rights that are
524 | specifically granted under this License. You may not convey a covered
525 | work if you are a party to an arrangement with a third party that is
526 | in the business of distributing software, under which you make payment
527 | to the third party based on the extent of your activity of conveying
528 | the work, and under which the third party grants, to any of the
529 | parties who would receive the covered work from you, a discriminatory
530 | patent license (a) in connection with copies of the covered work
531 | conveyed by you (or copies made from those copies), or (b) primarily
532 | for and in connection with specific products or compilations that
533 | contain the covered work, unless you entered into that arrangement,
534 | or that patent license was granted, prior to 28 March 2007.
535 |
536 | Nothing in this License shall be construed as excluding or limiting
537 | any implied license or other defenses to infringement that may
538 | otherwise be available to you under applicable patent law.
539 |
540 | 12. No Surrender of Others' Freedom.
541 |
542 | If conditions are imposed on you (whether by court order, agreement or
543 | otherwise) that contradict the conditions of this License, they do not
544 | excuse you from the conditions of this License. If you cannot convey a
545 | covered work so as to satisfy simultaneously your obligations under this
546 | License and any other pertinent obligations, then as a consequence you may
547 | not convey it at all. For example, if you agree to terms that obligate you
548 | to collect a royalty for further conveying from those to whom you convey
549 | the Program, the only way you could satisfy both those terms and this
550 | License would be to refrain entirely from conveying the Program.
551 |
552 | 13. Use with the GNU Affero General Public License.
553 |
554 | Notwithstanding any other provision of this License, you have
555 | permission to link or combine any covered work with a work licensed
556 | under version 3 of the GNU Affero General Public License into a single
557 | combined work, and to convey the resulting work. The terms of this
558 | License will continue to apply to the part which is the covered work,
559 | but the special requirements of the GNU Affero General Public License,
560 | section 13, concerning interaction through a network will apply to the
561 | combination as such.
562 |
563 | 14. Revised Versions of this License.
564 |
565 | The Free Software Foundation may publish revised and/or new versions of
566 | the GNU General Public License from time to time. Such new versions will
567 | be similar in spirit to the present version, but may differ in detail to
568 | address new problems or concerns.
569 |
570 | Each version is given a distinguishing version number. If the
571 | Program specifies that a certain numbered version of the GNU General
572 | Public License "or any later version" applies to it, you have the
573 | option of following the terms and conditions either of that numbered
574 | version or of any later version published by the Free Software
575 | Foundation. If the Program does not specify a version number of the
576 | GNU General Public License, you may choose any version ever published
577 | by the Free Software Foundation.
578 |
579 | If the Program specifies that a proxy can decide which future
580 | versions of the GNU General Public License can be used, that proxy's
581 | public statement of acceptance of a version permanently authorizes you
582 | to choose that version for the Program.
583 |
584 | Later license versions may give you additional or different
585 | permissions. However, no additional obligations are imposed on any
586 | author or copyright holder as a result of your choosing to follow a
587 | later version.
588 |
589 | 15. Disclaimer of Warranty.
590 |
591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
599 |
600 | 16. Limitation of Liability.
601 |
602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
610 | SUCH DAMAGES.
611 |
612 | 17. Interpretation of Sections 15 and 16.
613 |
614 | If the disclaimer of warranty and limitation of liability provided
615 | above cannot be given local legal effect according to their terms,
616 | reviewing courts shall apply local law that most closely approximates
617 | an absolute waiver of all civil liability in connection with the
618 | Program, unless a warranty or assumption of liability accompanies a
619 | copy of the Program in return for a fee.
620 |
621 | END OF TERMS AND CONDITIONS
622 |
623 | How to Apply These Terms to Your New Programs
624 |
625 | If you develop a new program, and you want it to be of the greatest
626 | possible use to the public, the best way to achieve this is to make it
627 | free software which everyone can redistribute and change under these terms.
628 |
629 | To do so, attach the following notices to the program. It is safest
630 | to attach them to the start of each source file to most effectively
631 | state the exclusion of warranty; and each file should have at least
632 | the "copyright" line and a pointer to where the full notice is found.
633 |
634 |
635 | Copyright (C)
636 |
637 | This program is free software: you can redistribute it and/or modify
638 | it under the terms of the GNU General Public License as published by
639 | the Free Software Foundation, either version 3 of the License, or
640 | (at your option) any later version.
641 |
642 | This program is distributed in the hope that it will be useful,
643 | but WITHOUT ANY WARRANTY; without even the implied warranty of
644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
645 | GNU General Public License for more details.
646 |
647 | You should have received a copy of the GNU General Public License
648 | along with this program. If not, see .
649 |
650 | Also add information on how to contact you by electronic and paper mail.
651 |
652 | If the program does terminal interaction, make it output a short
653 | notice like this when it starts in an interactive mode:
654 |
655 | Copyright (C)
656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
657 | This is free software, and you are welcome to redistribute it
658 | under certain conditions; type `show c' for details.
659 |
660 | The hypothetical commands `show w' and `show c' should show the appropriate
661 | parts of the General Public License. Of course, your program's commands
662 | might be different; for a GUI interface, you would use an "about box".
663 |
664 | You should also get your employer (if you work as a programmer) or school,
665 | if any, to sign a "copyright disclaimer" for the program, if necessary.
666 | For more information on this, and how to apply and follow the GNU GPL, see
667 | .
668 |
669 | The GNU General Public License does not permit incorporating your program
670 | into proprietary programs. If your program is a subroutine library, you
671 | may consider it more useful to permit linking proprietary applications with
672 | the library. If this is what you want to do, use the GNU Lesser General
673 | Public License instead of this License. But first, please read
674 | .
675 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Effective-Java-3rd-Edition-zh
2 |
3 | 📖 Effective Java (Third Edition) | Effective Java(第三版)翻译计划稿
4 |
5 | [](./LICENSE)
6 |
7 | # 目录
8 |
9 | + [**第 1 章 引言**](./1.引言/第一章%20引言.md)
10 | + [**第 2 章 创建和销毁对象**](./2.创建和销毁对象/第二章%20创建和销毁对象.md)
11 | - [第 1 条:考虑用静态工厂方法代替构造器](./2.创建和销毁对象/第%201%20条:考虑用静态工厂方法代替构造器.md)
12 | - [第 2 条:遇到多个构造器参数时要考虑用构建器](./2.创建和销毁对象/第%202%20条:遇到多个构造器参数时要考虑用构建器.md)
13 | - [第 3 条:用私有构造函数或者枚举类型强化 Singleton 属性](./2.创建和销毁对象/第%203%20条:用私有构造器或者枚举类型强化%20Singleton%20属性.md)
14 | - [第 4 条:通过私有构造函数强化不可实例化的能力](./2.创建和销毁对象/第%204%20条:通过私有构造器强化不可实例化能力.md)
15 | - [第 5 条:依赖注入优于资源硬连接](./2.创建和销毁对象/第%205%20条:依赖注入优于资源硬连接.md)
16 | - [第 6 条:避免创建不必要的对象](./2.创建和销毁对象/第%206%20条:避免创建不必要的对象.md)
17 | - [第 7 条:消除过期对象引用](./2.创建和销毁对象/第%207%20条:消除过期对象引用.md)
18 | - [第 8 条:避免使用 Finalizer 和 Cleaner 机制](./2.创建和销毁对象/第%208%20条:避免使用%20Finalizer%20和%20Cleaner%20机制.md)
19 | - [第 9 条:使用 try-with-resources 语句替代 try-finally 语句](./2.创建和销毁对象/第%209%20条:try-with-resources%20优于%20try-finally.md)
20 | + **[第 3 章 对所有对象都通用的方法](./3.对所有对象都通用的方法/第%203%20章%20对所有对象都通用的方法.md)**
21 | - [第 10 条:覆盖 equals 时请遵守通用约定](./3.对所有对象都通用的方法/第%2010%20条:覆盖%20equals%20时请遵守通用约定.md)
22 | - [第 11 条:覆盖 equals 时要覆盖 hashCode](./3.对所有对象都通用的方法/第%2011%20条:覆盖%20equals%20方法时要覆盖%20hashCode%20方法.md)
23 | - [第 12 条:始终要覆盖 toString](./3.对所有对象都通用的方法/第%2012%20条:始终要覆盖%20toString.md)
24 | - 第 13 条:谨慎地覆盖 clone
25 | - 第 14 条:考虑实现 Comparable 接口
26 | + **[第 4 章 类和接口](./4.类和接口/第四章%20类和接口.md)**
27 | + [第 15 条:最小化类及其成员的可访问性](./4.类和接口/第%2015%20条:最小化类及其成员的可访问性.md)
28 | + 第 16 条:在公共类中使用访问方法而非公有域
29 | + 第 17 条:最小化可变性
30 | + [(未校对)第 18 条:复合优先于继承](./4.类和接口/第%2018%20条:复合优于继承.md)
31 | + 第 19 条:设计并编写文档以供继承,否则就禁止继承
32 | + 第 20 条:接口优于抽象类
33 | + 第 21 条:为后代设计接口
34 | + 第 22 条:接口仅用于定义类型
35 | + 第 23 条:类层次优于标签类
36 | + 第 24 条:优先使用静态成员类
37 | + 第 25 条:将源文件限制为单个顶级类
38 | + **. . .**
39 |
40 | # 如何参与
41 |
42 | [点击这里,查看参与校对及翻译的正确姿势。](./.github/CONTRIBUTING.md)
43 |
44 | # 声明
45 |
46 | 本项目仅作为学习与交流使用,如果对你有所帮助,请购买正版书籍以示支持。
47 |
--------------------------------------------------------------------------------