├── .github ├── CONTRIBUTING.md └── ISSUE_TEMPLATE │ └── suggestions.md ├── 1.引言 └── 第一章 引言.md ├── 2.创建和销毁对象 ├── 第 1 条:考虑用静态工厂方法代替构造器.md ├── 第 2 条:遇到多个构造器参数时要考虑用构建器.md ├── 第 3 条:用私有构造器或者枚举类型强化 Singleton 属性.md ├── 第 4 条:通过私有构造器强化不可实例化能力.md ├── 第 5 条:依赖注入优于资源硬连接.md ├── 第 6 条:避免创建不必要的对象.md ├── 第 7 条:消除过期对象引用.md ├── 第 8 条:避免使用 Finalizer 和 Cleaner 机制.md ├── 第 9 条:try-with-resources 优于 try-finally.md └── 第二章 创建和销毁对象.md ├── 3.对所有对象都通用的方法 ├── 第 10 条:覆盖 equals 时请遵守通用约定.md ├── 第 11 条:覆盖 equals 方法时要覆盖 hashCode 方法.md ├── 第 12 条:始终要覆盖 toString.md └── 第三章 对所有对象都通用的方法.md ├── 4.类和接口 ├── 第 15 条:最小化类及其成员的可访问性.md ├── 第 18 条:复合优于继承.md └── 第四章 类和接口.md ├── LICENSE └── README.md /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## 参与校对/翻译 4 | 5 | 欢迎你提出 issue,或申请 pull request 帮助我们改进翻译。以下一些信息能帮助尽快了解本翻译计划: 6 | 7 | 1. **翻译所需资源**已放在网盘中,请[**点击下载**](https://pan.baidu.com/s/11ZzwKKiasGcOS-lljt2muQ)。 8 | 2. **为方便阅读及减少沟通成本**,本翻译参照[**中文文案排版指北**](https://github.com/sparanoid/chinese-copywriting-guidelines)进行中英文排版,请你理解并遵守。 9 | 3. **若准备翻译新的章节**,为避免发生冲突,建议在 issues 中以“[认领翻译]章节-条目”,形如 `[认领翻译]chapter02-item03` 作为标题进行说明。翻译完毕或者放弃翻译时,希望你能主动关闭 issue。**注意**:新的翻译章节添加时,请一并修改 README 中的目录信息,并在新添加章节名前添加“[未校对]”字样,形如“[[未校对]第 7 条:消除过期对象引用](./2.创建和销毁对象/第%207%20条:消除过期对象引用.md)”。该字样后续由校对者对文章校对后再进行删除。 10 | 4. **若准备校对未校对章节**,请在 pull requests 中以“翻译完成”为关键字或标签进行搜索,在对应 PR 下回复“校对认领”即可认领该章节的校对。**注意**:校对完成之后请 `@译者` 告知校对完成,方便及时进行沟通。 11 | 5. **Fork 此仓库后**,请先从 master 分支上 `git checkout -b "章节名-条目名"(形如"chapter02-item03")` 一个新的分支来翻译/校对文章。翻译/校对完成后再把这个分支发 PR,标题格式形如 `[翻译/校对完成]chapter02-item03`。 12 | 6. **为方便查看历史信息**,commit 时建议参照 [**gitmoji**](https://gitmoji.carloscuesta.me/) 或 [**git commit message emoji 使用指南**](https://github.com/liuchengxu/git-commit-emoji-cn)。 13 | 7. **对于引用和超链接的排版**请参照[`第 1 条:考虑用静态工厂方法代替构造器`](https://github.com/learning-and-thinking/Effective-Java-3rd-Edition-zh/blob/master/2.%E5%88%9B%E5%BB%BA%E5%92%8C%E9%94%80%E6%AF%81%E5%AF%B9%E8%B1%A1/%E7%AC%AC%201%20%E6%9D%A1%EF%BC%9A%E8%80%83%E8%99%91%E7%94%A8%E9%9D%99%E6%80%81%E5%B7%A5%E5%8E%82%E6%96%B9%E6%B3%95%E4%BB%A3%E6%9B%BF%E6%9E%84%E9%80%A0%E5%99%A8.md)的处理方式。 14 | 8. **文末**同样建议像[`第 1 条:考虑用静态工厂方法代替构造器`](https://github.com/learning-and-thinking/Effective-Java-3rd-Edition-zh/blob/master/2.%E5%88%9B%E5%BB%BA%E5%92%8C%E9%94%80%E6%AF%81%E5%AF%B9%E8%B1%A1/%E7%AC%AC%201%20%E6%9D%A1%EF%BC%9A%E8%80%83%E8%99%91%E7%94%A8%E9%9D%99%E6%80%81%E5%B7%A5%E5%8E%82%E6%96%B9%E6%B3%95%E4%BB%A3%E6%9B%BF%E6%9E%84%E9%80%A0%E5%99%A8.md)一样,添加翻译或是校对者的简要信息。 15 | 16 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/suggestions.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 翻译修改建议 3 | about: 若翻译有问题请提出以帮助我们进行修改 4 | 5 | --- 6 | 7 | **问题描述** 8 | 9 | 请简明扼要的描述问题所在 10 | 11 | `比如 关于 xx 翻译,表述的不够清晰` 12 | 13 | **问题出现的位置** 14 | 15 | 1. 哪一章 `比如 2` 16 | 2. 哪一条 `比如 2` 17 | 3. 哪一行 `比如 1` 18 | 4. 哪一句 `比如 xxx xxx` 19 | 20 | **修改建议** 21 | 22 | 请简明扼要的提出你所希望的修改是什么以及原因 23 | 24 | `比如,关于上述的 xxxx 我觉得可以改成 xxxx,因为 xxxx` 25 | 26 | **额外问题及建议** 27 | -------------------------------------------------------------------------------- /1.引言/第一章 引言.md: -------------------------------------------------------------------------------- 1 | # 第一章 引言 2 | 3 | 本书旨在帮助你高效地使用 Java 及其基础类库:java.lang,java.util 和 java.io 及其子包下的诸如 java.util.concurrent 和 java.util.function。其它的库也会不时地讨论一下。 4 | 5 | ​本书共由 90 个条目组成,每一条都传达了一条规则。这些规则反映了最有经验的优秀程序员在实践中常用的一些有益做法。这些条目被松散地分为了 11 个章节,每个章节都传达了软件设计广泛的一面。各个条目间或多或少保持独立,不需要刻意地将本书从头读到尾。条目间在很大程度上相互引用,所以你可以很容易地通过本书规划自己的路线。 6 | 7 | ​自上一版书发布以来,该平台增加了很多新的特性。本书中的很多条目以一定的方式使用了这些特性,下面这张表展示了这些关键特性所在的主要范围: 8 | 9 | | 特性 | 所在条目 | 发行版本 | 10 | | :---------------------- | :------------ | -------- | 11 | | Lambdas | 第 42 ~ 44 条 | Java 8 | 12 | | Streams | 第 45 ~ 48 条 | Java 8 | 13 | | Optionals | 第 55 条 | Java 8 | 14 | | 接口默认方法 | 第 21 条 | Java 8 | 15 | | try-with-resources 语句 | 第 9 条 | Java 7 | 16 | | @SafeVarargs | 第 32 条 | Java 7 | 17 | | 模块化 | 第 15 条 | Java 9 | 18 | 19 | ​大多数条目都通过程序示例进行说明。本书的一个主要特点是,它包含了很多描述设计模式(Design Patterns)和习惯用法(Idioms)的代码示例。在恰当的地方,还为这些设计模式和习惯用法交叉引用至该领域的标准参考书 [[Gamma95](#Gamma95)]。 20 | 21 | ​许多条目都包含了一个或多个应该在实践中避免的程序示例。像这样的例子,有时候被认为是“反模式”(Antipatterns),其被清晰的标注了 **`//Never do this!`**。在每种情况中,条目都解释了为什么这个示例是不好的,并且提供了解决的方法。 22 | 23 | ​本书不是面向初学者的:本书假设你已经熟悉 Java 了。如果你还没有,可以考虑一本不错的入门书籍,比如 Peter Sestoft 的 [*Java Precisely Sestoft16*](#Sestoft16)。既然 *Effective Java* 旨在让任何具有 Java 语言知识的人都可以使用,那它也应该为高级程序员提供精神食粮。 24 | 25 | ​本书中大多数规则都是从少数的几条原则推导出来的。清晰和简洁至关重要。组件的使用者不应该对组件的表现行为感到疑惑。组件应该尽可能的小,但又不能太小(本书中所使用的术语“组件”(component),是指任何可重用的软件元素,从单个方法到由多个包组成的复杂框架都可以是一个组件)。代码应该是被重用,而不是被拷贝。组件之间的依赖性应该保持在最低限度。出现错误应尽可能被检测到,最理想的就是在编译期。 26 | 27 | ​虽然本书中的规则并不会百分之百的适用任何场景,但是,它们的确体现了绝大部分场景的最佳编程实践。你不应该亦步亦趋地遵从这些规则,而应该只是偶尔并且具有合理理由的情况下才违反这些规则。同大多数学科一样,学习编程的艺术,首先要学习它的规则,然后才学习何时打破它们。 28 | 29 | ​本书的大部分内容不是在讨论性能如何,而是在讨论如何写出清晰,正确,可用,健壮,灵活和可维护的代码。如果你能做到这些,那么获得所需要的的性能就是一件相对简单的事情了([见第 67 条][item67])。一些条目的确讨论了性能问题,并且有的还提供了性能指标。这些性能指标都被标明了“在我的机器上(On my machine)”,所以应该把这些指标当作是近似值。 30 | 31 | ​值得提及的是,我(指作者)的机器是一台老旧的自制电脑,主机为 3.5 GHz 四核英特尔酷睿 i7-4770K,配备 16G 的 DDR3-1866 CL9 内存,在 Microsoft Windows 7 Professional SPI (64位)操作系统上运行 Azul's Zulu 9.0.0.15 发行版本的 OpenJDK。 32 | 33 | ​当讨论 Java 编程语言的特性及其类库时,有时候必须要指明具体的发行版本。为了简便起见,本书使用昵称而不是官方的发行名称,这张表展示了发行名称和昵称的对应关系: 34 | 35 | | 官方发行名称 | 昵称 | 36 | |---|---| 37 | |JDK 1.0.x |Java 1.0| 38 | |JDK 1.1.x |Java 1.1| 39 | |Java 2 Platform, Standard Edition, v1.2 |Java 2| 40 | |Java 2 Platform, Standard Edition, v1.3 |Java 3| 41 | |Java 2 Platform, Standard Edition, v1.4 |Java 4| 42 | |Java 2 Platform, Standard Edition, v5.0 |Java 5| 43 | |Java Platform, Standard Edition 6 |Java 6| 44 | |Java Platform, Standard Edition 7 |Java 7| 45 | |Java Platform, Standard Edition 8 |Java 8| 46 | |Java Platform, Standard Edition 9 |Java 9| 47 | 48 | ​尽管这些例子相当完整,但是可读性要高于完整性。它们直接使用了 java.util 和 java.io 中的类,为了编译示例,你可能需要添加一行甚至多行 import 语句。本书的网站 [http://joshblock.com/effectivejava](http://joshblock.com/effectivejava),包含了每个示例的完整版本,你可以直接编译运行它们。 49 | 50 | ​在大多数情况下,本书使用了《The Java Language Specification,Java SE 8 Edition》[[JLS](#JLS)] 中定义的技术术语。这其中有一些术语值得特别提及。Java 语言支持 4 种类型:接口(包括注解)、类(包括枚举)、数组和基本类型(primitive),前面 3 种被称为引用类型(reference type),类实例和数组是对象(object),而基本类型的值则不是。类的成员(member)由属性,方法,成员类和成员接口组成。方法的签名(signature)是由它的名称和形式参数类型组成,签名不包含方法的返回类型。 51 | 52 | ​本书也使用了一些与 《The Java Language Specification》 不同的术语。与 《The Java Language Specification》不同的是,本书用术语“继承(inheritance)”作为“子类化(subclassing)”的同义词。本书不再使用“接口继承”这个术语,而是简单地描述为一个类实现(implement)了一个接口,或者一个接口扩展(extend)了另一个接口。为了描述“在没有指定访问级别的情况下所使用的访问级别”,本书使用了传统的“包级私有(package-private)”而不是在 [[JLS, 6.6.1](#JLS)] 中技术上正确的 “包访问(package-access)”。 53 | 54 | ​本书也使用了一些在《The Java Language Specification》 中没有定义的技术术语。术语“导出的 API (exported API)”,或者简单地说 API ,是指类、接口、构造器(constructor)、成员和序列化形式(serialized form),程序员通过它们可以访问类、接口或者包。(术语 API 是 Application Programming Interface 的简写,这里之所以使用 API 而不用接口(interface),是为了不与 Java 语言中的 interface 类型混淆)。使用 API 编写程序的程序员被称为该 API 的用户(user),使用 API 实现的类被称为是该 API 的客户(client)。 55 | 56 | ​类、接口、构造器、成员和序列化形式被统称为 API 元素(API element)。导出的 API 由可在定义该 API 的包之外访问的 API 元素组成。任何客户端都可以使用这些 API 元素,API 的作者则负责支持这些 API 元素。并非巧合的是,Javadoc 工具类在默认操作模式下正是为这些元素生成文档。不严格地讲,一个包的导出 API 由该包中每个公有类或者接口中的公有(public)成员和受保护的(protected)成员以及构造器组成。 57 | 58 | ​在 Java 9 中,模块系统加入到了这个平台。如果一个库使用了模块系统,那么其导出的 API 就是库的模块声明导出的所有包的导出 API 的并集。 59 | 60 |

[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.

61 | 62 |

[Sestoft16]
Sestoft, Peter. 2016. Java Precisely, 3rd ed. Cambridge, MA: The MIT Press.
ISBN: 0262529076.

63 | 64 |

[JLS]
Gosling, James, Bill Joy, Guy Steele, and Gilad Bracha. 2014. The Java
Language Specification, Java SE 8 Edition. Boston: Addison-Wesley. ISBN:
013390069X.

65 | 66 | [item67]: url-for-item-67 "在未来填入第67条的url,不然无法跳转到指定网页" 67 | 68 | --- 69 | 70 | > 翻译:Inno 71 | > 72 | > 校对:Angus 73 | -------------------------------------------------------------------------------- /2.创建和销毁对象/第 1 条:考虑用静态工厂方法代替构造器.md: -------------------------------------------------------------------------------- 1 | # 第 1 条:考虑用静态工厂方法代替构造器 2 | 3 | 对于类而言,允许客户端获取其实例的传统方式是提供一个公有的构造器。但还有一种方法,也应该在每个程序员的工具箱中占有一席之地。类可以提供一个公有的静态工厂方法(static factory method),它只是一个返回类的实例的静态方法。下面是一个 `Boolean` 类型(基本类型 `boolean` 的包装类)的简单示例。这个方法将一个 `boolean` 基本类型值转换成了一个 `Boolean` 对象引用: 4 | 5 | ```java 6 | public static Boolean valueOf(boolean b) { 7 | return b ? Boolean.TRUE : Boolean.FALSE; 8 | } 9 | ``` 10 | 11 | 注意,静态工厂方法和设计模式(design patterns)中的工厂方法模式(factory method pattern)并不相同 [[Gamma95](#Gamma95)]。本条目描述的静态工厂方法在设计模式中没有直接的对应项。 12 | 13 | 类可以为其客户端提供静态工厂方法,而不是公有构造器,或者说除了公有构造器之外,提供静态工厂方法而非公有构造器既有优点也有缺点。 14 | 15 | **静态工厂方法相比于构造器的第一个优点是,它们具有名称。** 如果构造器的参数本身没有描述返回的对象,那么具有适当名称的静态工厂会更易于使用,客户端生成的代码也更易于阅读。例如,构造器 `BigInteger(int, int, Random)` 返回的 `BigInteger` 可能为素数,它可以更好地用一个名为 `BigInteger.probablePrime` 的静态工厂方法来表示。(此方法是在 Java 4 中添加的。) 16 | 17 | 一个类只能有一个具有给定签名的构造器。程序员可以通过提供两个构造器来避开这个限制,而这两个构造器的参数列表仅在于参数类型的顺序不同。不过这是个非常糟糕的主意。因为这种 API 的用户将永远无法分清该使用哪一个,最终将会导致错误的调用。在没有参考文档的情况下,当有人阅读使用了这些构造器的代码时,也无法明白其中的意图。 18 | 19 | 因为静态方法有名称,所以不受上述限制。如果某个类需要多个具有相同签名的构造器时,请使用静态工厂方法替换构造器,并且慎重地选择名称来突出它们之间的差异。 20 | 21 | **静态工厂方法相比于构造器的第二个优点是,不需要在每次被调用时都创建一个新对象。** 这允许不可变类([第 17 条][item17])使用预先构建的实例,或者缓存构造好的实例,进行重复分配,以避免创建不必要的重复对象。`Boolean.valueOf(boolean)` 方法就说明了这种技术:它从来不会创建对象。这种技术类似于享元模式(flyweight pattern) [[Gamma95](#Gamma95)]。如果经常请求相同的对象,特别是它们的创建成本较高,则这种方法可以极大地提高性能。 22 | 23 | 静态工厂方法被重复调用时返回相同对象的能力,有助于类在任何时候严格控制哪些实例应该存在。执行此操作的类被称为实例受控的类(instance-controlled)。编写实例受控的类有几个原因。实例受控允许类保证它是一个单例(singleton [第 3 条][item3])或是不可实例化的(noninstantiable [第 4 条][item4])。此外,它使得不可变类(immutable value class [第 17 条][item17])保证不存在两个相等的实例:即 `a.equals(b)` 为真当且仅当 `a == b` 为真 。这就是享元模式 [[Gamma95](#Gamma95)] 的基础。枚举(enum)类型([第 34 条][item34])保证了这一点。 24 | 25 | **静态工厂方法相比于构造器的第三个优点是,它们可以返回原返回类型的任何子类型的对象。** 这使得你在选择返回对象的类时具有很大的灵活性。 26 | 27 | 这种灵活性的一个应用是,API 可以在不使得类变得公有的情况下返回其对象。以这种方式隐藏实现类会让 API 非常简洁紧凑。这种技术适用于基于接口的框架(interface-based framework [第 20 条][item20]),其中接口为静态工厂方法提供自然返回类型。 28 | 29 | 在 Java 8 之前,接口不能有静态方法。按照惯例,接口 Type 的静态工厂方法放在名为 Types 的不可实例化的伴随类中([第 4 条][item4])。例如,Java 集合框架(java collections framework)的接口有 45 个便利的实现,提供了不可修改的集合、同步集合等。几乎所有的这些实现都是通过一个不可实例化类(`java.util.Collections`)中的静态工厂方法导出的。所有返回对象的类都是非公有的。 30 | 31 | 每种便利实现都对应一个类,使得现在的集合框架 API 比由它直接导出 45 个独立的公有类的实现方式要小得多。这不仅仅减少了 API 的数量,也减轻了“概念权重”(conceptual weight):程序员为了使用 API 而必须掌握的概念的数量和难度。因为程序员知道,返回的对象是由相关接口的 API 精确指定的,所以不需要为实现类(implementation class)而阅读额外的文档。此外,使用这种静态工厂方法需要客户端通过接口而不是实现类来引用返回的对象,这通常是一种很好的做法([第 64 条][item64])。 32 | 33 | 从 Java 8 开始,接口不能包含静态方法的限制被取消,因此,通常没有理由~~再~~为接口提供不可实例化的伴随类,许多在在这个类中的公共静态成员应该放在接口本身中。但请注意,可能仍有必要将这些静态方法背后的实现放在一个单独的包私有(package-private)类中。这是因为 Java 8 要求接口的所有静态成员都是公有的。Java 9 允许私有静态方法,但静态属性和静态成员类仍然需要公开。 34 | 35 | **静态工厂方法的第四个优点是,其作为输入参数的函数,返回对象的类可以随着调用而变化。** 只要是已声明返回类型的子类型都是允许的。返回对象的类也可能因发行版本不同而有差异。 36 | 37 | `EnumSet` 类([第 36 条][item37])没有公共构造器,只有静态工厂方法。在 OpenJDK 的实现中,取决于底层枚举类型的大小,这些静态工厂方法会返回两个实现类中的一个的实例:像大多数枚举类型那样,如果元素数目为 64 个或更少,静态工厂方法会返回一个 `RegularEnumSet` 实例,由单一的 `long` 提供支持;如果枚举类型包含 65 个或更多元素,则工厂会返回一个由 `long` 数组支持的 `JumboEnumSet` 实例。 38 | 39 | 这两个实现类的存在对于客户端来说是不可见的。如果 `RegularEnumSet` 不能再为小型枚举类型提供性能优势,可以从未来的发行版本中去掉,这没有任何不良影响。同样,如果被证明对性能是有益的,未来的发行版本还可以添加 `EnumSet` 的第三或第四个实现。客户既不需要知道也不必关心他们从工厂方法获得的对象的类,只要知道它是 `EnumSet` 的子类即可。 40 | 41 | **静态工厂方法的第五个优点是,当编写包含该静态工厂方法的类时,返回对象所属的实现类不需要存在。** 这种灵活的静态工厂方法构成了服务提供者框架(service provider framework)的基础,如 Java 数据库连接 API(JDBC)。服务提供者框架是多个服务提供者实现一个服务的系统,该系统为客户端提供多个适用的实现,并将客户端与实现解耦。 42 | 43 | 服务提供者框架中有三个重要的组件:首先是服务接口(Service Interface),它代表服务的一个实现; 其次是提供者注册 API(Provider Registration API),用于提供者注册服务的实现;最后是服务访问 API(Service Access API),客户端使用它来获取服务的实例。服务访问 API 一般允许客户端指定选择实现的标准。在没有这种标准的情况下,API 将返回默认实现的一个实例,或是允许客户端循环遍历所有可用的实现。服务访问 API 即是灵活的静态工厂,它构成了服务提供者框架的基础。 44 | 45 | 服务提供者框架的第四个组件服务提供者接口(Service Provider Interface)是可选的,它描述了一个生成服务接口实例的工厂对象。在没有服务提供者接口的情况下,服务的实现必须通过反射的方式进行实例化([第 65 条][item65])。对于 JDBC 来说,`Connection` 扮演了服务接口的一部分,`DriverManager.registerDriver` 是提供者注册API,`DriverManager.getConnection` 是服务访问API,而 `Driver` 就是服务提供者接口。 46 | 47 | 服务提供者框架模式有许多变体。例如,服务访问 API 可以向客户端返回比提供者所提供的更丰富的服务接口。桥接模式(bridge pattern)就起到了这样的作用 [[Gamma95](#Gamma95)]。依赖注入框架(dependency injection framework [第 5 条][item5])可视为一个强大的服务提供者。从 Java 6 开始,该平台包含了一个通用(general-purpose)服务提供者框架:`java.util.ServiceLoader` ,因此你不需要(通常不应该)编写自己的服务提供者框架([第 59 条][item59])。JDBC 早于 `ServiceLoader`,所以 JDBC 没有使用该框架。 48 | 49 | **仅提供静态工厂方法的主要限制是,类如果没有公有或受保护的构造器,就不能被子类化(继承)。** 例如,子类化集合框架(Collections Framework)中的任何便捷实现类都是不可能的。但这可能因祸得福,因为它鼓励程序员使用组合(composition)而不是继承([第 18 条][item18]),对于不可变类型([第 17 条][item17])而言,这是必需的。 50 | 51 | **静态工厂方法的第二个缺点是,程序员很难找到它们。** 在 API 文档中,它们并不像构造器这样突出,因此,很难弄清楚如何实例化一个只提供静态工厂方法而没有构造器的类。Javadoc 工具有一天可能会关注静态工厂方法。在此期间,你可以通过关注类或接口文档中的静态工厂方法,并遵循常见的命名约定来弥补这一劣势。以下是静态工厂方法的一些常用命名。当然,这份清单远非详尽: 52 | 53 | + **from** —— 一种类型转换方法(type-conversion method),它接收单个参数并返回此类型的相应实例,例如: 54 | 55 | ```java 56 | Date d = Date.from(instant); 57 | ``` 58 | 59 | + **of** —— 一种聚合方法(aggregation method),它接受多个参数,返回包含这些参数的该类型的实例,例如: 60 | 61 | ```java 62 | Set faceCards = EnumSet.of(JACK, QUEEN, KING); 63 | ``` 64 | 65 | + **valueOf** —— 较 `from` 和 `of` 更为详细的可选方案,例如: 66 | 67 | ```java 68 | BigInteger prime = BigInteger.valueOf(Integer.MAX_VALUE); 69 | ``` 70 | 71 | + **instance 或 getInstance** —— 返回由其参数(如果有)描述的实例,但不能说实例与参数具有相同的值,例如: 72 | 73 | ```java 74 | StackWalker luke = StackWalker.getInstance(options); 75 | ``` 76 | 77 | + **create 或 newInstance** —— 同 `instance` 或 `getInstance` 相似,但该方法需要保证每次调用时都返回一个新的实例,例如: 78 | 79 | ```java 80 | Object newArray = Array.newInstance(classObject, arrayLen); 81 | ``` 82 | 83 | + **get*Type*** —— 同 `getInstance` 相似,但它是在工厂方法位于不同的类中时才使用。*Type* 表示该工厂方法返回的对象类型,例如: 84 | 85 | ```java 86 | FileStore fs = Files.getFileStore(path); 87 | ``` 88 | 89 | + **new*Type*** —— 同 `newInstance` 相似,但它是在工厂方法位于不同的类中时才使用。*Type* 表示该工厂方法返回的对象类型,例如: 90 | 91 | ```java 92 | BufferedReader br = Files.newBufferedReader(path); 93 | ``` 94 | 95 | + **type** —— `getType` 和 `newType` 的简洁替代,例如: 96 | 97 | ```java 98 | List litany = Collections.list(legacyLitany); 99 | ``` 100 | 101 | 总之,静态工厂方法和公有构造器都有用处,理解它们各自的优缺点是值得的。通常,静态工厂更可取,因此在没有优先考虑静态工厂的情况下,要避免习惯性地提供公有构造器。 102 | 103 |

[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.

104 | 105 | [item3]: ./第%203%20条:用私有构造器或者枚举类型强化%20Singleton%20属性.md "第 3 条:用私有构造器或者枚举类型强化 Singleton 属性" 106 | [item4]: ./第%204%20条:通过私有构造器强化不可实例化能力.md "第 4 条:通过私有构造器强化不可实例化能力" 107 | [item5]: ./第%205%20条:依赖注入优于资源硬连接.md "第 5 条:依赖注入优于资源硬连接" 108 | [item17]: url-for-item-17 "在未来填入第17条的url,不然无法跳转到指定网页" 109 | [item18]: url-for-item-18 "在未来填入第18条的url,不然无法跳转到指定网页" 110 | [item20]: url-for-item-20 "在未来填入第20条的url,不然无法跳转到指定网页" 111 | [item34]: url-for-item-34 "在未来填入第34条的url,不然无法跳转到指定网页" 112 | [item37]: url-for-item-37 "在未来填入第37条的url,不然无法跳转到指定网页" 113 | [item59]: url-for-item-59 "在未来填入第59条的url,不然无法跳转到指定网页" 114 | [item64]: url-for-item-64 "在未来填入第64条的url,不然无法跳转到指定网页" 115 | [item65]: url-for-item-65 "在未来填入第65条的url,不然无法跳转到指定网页" 116 | 117 | > 翻译:Angus 118 | > 119 | > 校对:Inno 120 | -------------------------------------------------------------------------------- /2.创建和销毁对象/第 2 条:遇到多个构造器参数时要考虑用构建器.md: -------------------------------------------------------------------------------- 1 | # 第 2 条 :遇到多个构造器参数时要考虑用构建器 2 | 3 | 静态工厂和构造器有一个共同的局限:它们都不能很好地扩展到大量的可选参数。考虑用一个类来表示食品包装上的营养成分标签的情况。这些标签中有一些是必需的属性:食用份量(serving size),每罐份量(servings per container)以及每份的卡路里(calories per serving),还有超过 20 个可选的属性:总脂肪量(total fat)、饱和脂肪量(saturated fat)、转化脂肪(trans fat)、胆固醇(cholesterol)、钠(sodium)等等。大多数产品仅仅只有少部分可选属性有非零值。 4 | 5 | 对于这样的类,应该编写什么样的构造器或静态工厂呢? 传统的写法,程序员会使用重叠构造器模式(telescoping constructor pattern),在这种模式下,你提供的第一个构造器只需带有必要参数,而第二个构造器可以带有一个可选参数,第三个构造器带有两个可选参数,以此类推,最后一个构造器囊括所有可选参数。下面是一个示例,为了简单起见,只展示了四个可选参数: 6 | 7 | ```java 8 | // 重叠构造器模式 - 不能很好地扩展! 9 | public class NutritionFacts { 10 | private final int servingSize; // (毫升) 必需 11 | private final int servings; // (每罐) 必需 12 | private final int calories; // (每份) 可选 13 | private final int fat; // (克每份) 可选 14 | private final int sodium; // (毫克每份) 可选 15 | private final int carbohydrate; // (克每份) 可选 16 | 17 | public NutritionFacts(int servingSize, int servings) { 18 | this(servingSize, servings, 0); 19 | } 20 | 21 | public NutritionFacts(int servingSize, int servings, int calories) { 22 | this(servingSize, servings, calories, 0); 23 | } 24 | 25 | public NutritionFacts(int servingSize, int servings, int calories, int fat) { 26 | this(servingSize, servings, calories, fat, 0); 27 | } 28 | 29 | public NutritionFacts(int servingSize, int servings, int calories, int fat, int sodium) { 30 | this(servingSize, servings, calories, fat, sodium, 0); 31 | } 32 | 33 | public NutritionFacts(int servingSize, int servings, int calories, int fat, int sodium, int carbohydrate) { 34 | this.servingSize = servingSize; 35 | this.servings = servings; 36 | this.calories = calories; 37 | this.fat = fat; 38 | this.sodium = sodium; 39 | this.carbohydrate = carbohydrate; 40 | } 41 | } 42 | ``` 43 | 44 | 当你想要创建实例时,可以使用参数列表最短的构造器,其包含了所有你想要设置的参数。 45 | 46 | ```java 47 | NutritionFacts cocaCola = new NutritionFacts(240, 8, 100, 0,35, 27); 48 | ``` 49 | 50 | 通常,调用该构造器将需要许多你不想要设置的参数,但无论如何你都要为它们传递一个值。在这个例子中,我们为 fat 这个变量传递了一个为 0 的值。“仅仅” 6 个参数,这看起来还不算太糟,但随着参数数量的增加,这将很快失控。 51 | 52 | 简而言之,**重叠构造器模式可行,但是当参数很多时,客户端代码会很难编写,并且仍然难以阅读**。读者会留下疑问:这些值到底是什么意思,并且必须仔细的数着这些参数来探个究竟。一长串相同类型的参数会造成一些微妙的错误。如果客户端意外的弄反了其中两个参数,编译器不会报错,但是在运行时程序会出现错误的行为。 53 | 54 | 当遇到一个有很多参数的构造器时,第二个备选方案就是 JavaBeans 模式,在这个模式中,你可以调用一个无参的构造器来创建对象,并在之后使用 setter 方法来设置必需必要参数以及感兴趣的可选参数。 55 | 56 | ```java 57 | // JavaBeans 模式 - 允许不一致性、任务可变性 58 | public class NutritionFacts { 59 | // 参数初始化为默认值(如果有的话) 60 | private int servingSize = -1; // 必需;没有默认值 61 | private int servings = -1; // 必需;没有默认值 62 | private int calories = 0; 63 | private int fat = 0; 64 | private int sodium = 0; 65 | private int carbohydrate = 0; 66 | 67 | public NutritionFacts() { 68 | } 69 | 70 | // Setter 方法 71 | public void setServingSize(int val) { 72 | servingSize = val; 73 | } 74 | 75 | public void setServings(int val) { 76 | servings = val; 77 | } 78 | 79 | public void setCalories(int val) { 80 | calories = val; 81 | } 82 | 83 | public void setFat(int val) { 84 | fat = val; 85 | } 86 | 87 | public void setSodium(int val) { 88 | sodium = val; 89 | } 90 | 91 | public void setCarbohydrate(int val) { 92 | carbohydrate = val; 93 | } 94 | } 95 | ``` 96 | 97 | 这个模式没有重叠构造器模式的缺点。说明白点,就是创建一个实例很容易,并且生成的代码易于读懂。 98 | 99 | ```java 100 | NutritionFacts cocaCola = new NutritionFacts(); 101 | cocaCola.setServingSize(240); 102 | cocaCola.setServings(8); 103 | cocaCola.setCalories(100); 104 | cocaCola.setSodium(35); 105 | cocaCola.setCarbohydrate(27); 106 | ``` 107 | 108 | 遗憾的是,JavaBeans 模式本身有着严重的缺点。由于构造过程被拆分到了多个调用中,**因而,JavaBean 可能在构造中途处于不一致状态**。类没有办法仅仅通过校验构造参数的有效性来保证一致性。尝试在对象处于不一致状态时使用可能会导致失败,这种失败与包含错误的代码大相径庭,因此难以调试。与之相关的另一个缺点在于, **JavaBeans 模式阻止了把类变成不可变的可能性**([第 17 条](item17)), 这就需要程序员付出更多的努力来确保线程安全。 109 | 110 | It is possible to reduce these disadvantages by manually “freezing” the object when its construction is complete and not allowing it to be used until frozen, but this variant is unwieldy and rarely used in practice. Moreover, it can cause errors at runtime because the compiler cannot ensure that the programmer calls the freeze method on an object before using it. 111 | 112 | 可以通过在构建完成时手动“冻结(freezing)”对象来弥补这些缺陷,并且在“冻结”之前不允许该对象被使用, 但是这种方式十分笨拙,并且在实践中很少使用。此外,它还会在运行时造成错误,因为编译器不能确定程序员会在使用之前先在对象上调用 freeze 方法。 113 | 114 | 幸运的是,还有第三种备选方案,它兼具了重叠构造器模式的安全性和 JavaBeans 模式的可读性,这就是 Builder 模式 [[Gamma95](#Gamma95)] 的一种形式。与直接创建想要的对象的方式不同,客户端调用带有所有必需参数的构造器(或者静态工厂)并获得一个 builder 对象。然后客户端在 builder 对象上调用类似于 setter 的方法为感兴趣的可选参数赋值。最后,客户端调用无参的 build 方法来生成对象,该对象通常是不可变的。Builder 通常是它构建的类的一个静态成员类([第 24 条][item24]),下面是它的示例: 115 | 116 | ```java 117 | // Builder 模式 118 | public class NutritionFacts { 119 | private final int servingSize; 120 | private final int servings; 121 | private final int calories; 122 | private final int fat; 123 | private final int sodium; 124 | private final int carbohydrate; 125 | 126 | public static class Builder { 127 | // 必需参数 128 | private final int servingSize; 129 | private final int servings; 130 | 131 | // 可选参数,初始化默认值 132 | private int calories = 0; 133 | private int fat = 0; 134 | private int sodium = 0; 135 | private int carbohydrate = 0; 136 | 137 | public Builder(int servingSize, int servings) { 138 | this.servingSize = servingSize; 139 | this.servings = servings; 140 | } 141 | 142 | public Builder calories(int val) { 143 | calories = val; 144 | return this; 145 | } 146 | 147 | public Builder fat(int val) { 148 | fat = val; 149 | return this; 150 | } 151 | 152 | public Builder sodium(int val) { 153 | sodium = val; 154 | return this; 155 | } 156 | 157 | public Builder carbohydrate(int val) { 158 | carbohydrate = val; 159 | return this; 160 | } 161 | 162 | public NutritionFacts build() { 163 | return new NutritionFacts(this); 164 | } 165 | } 166 | 167 | private NutritionFacts(Builder builder) { 168 | servingSize = builder.servingSize; 169 | servings = builder.servings; 170 | calories = builder.calories; 171 | fat = builder.fat; 172 | sodium = builder.sodium; 173 | carbohydrate = builder.carbohydrate; 174 | } 175 | } 176 | ``` 177 | 178 | NutritionFacts 类是不可变的,并且所有默认参数值都在一个地方。builder 的 settter 方法会返回 builder 本身,以便可以链接调用,形成流式 API。下面是客户端代码: 179 | 180 | ```java 181 | NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8) 182 | .calories(100) 183 | .sodium(35) 184 | .carbohydrate(27) 185 | .build(); 186 | ``` 187 | 188 | 这样的客户端代码易于编写,更重要的是易于阅读。**Builder 模式模拟了 Python 和 Scala 中的命名可选参数**。 189 | 190 | 为了简洁,省略了有效性检查。若要尽快检测出无效参数,请在 builder 的构造器和方法中检查参数有效性。检查由 build 方法调用的构造器中涉及多个参数的不变量。为了确保这些不变量不受攻击,可以将 builder 中的参数拷贝到对象之后,对对象字段进行校验 ([第 50 条][item50])。如果校验失败,应该抛出 IllegalArgumentException 异常([第 72 条][item72]),其详细信息指明了哪个参数是无效的([第 75 条][item75])。 191 | 192 | **Builder 模式十分适合于类层次结构(class hierarchy)。** 使用 builder 的并行层次结构,每个 builder 都嵌套在相应的类中。抽象类有抽象的 builder;具体类有具体的 builder。例如,考虑一个在层级结构根部的用来表示多种类型的披萨的抽象类: 193 | 194 | ```java 195 | // 类层级结构的 builder 模式 196 | public abstract class Pizza { 197 | public enum Topping { 198 | HAM, MUSHROOM, ONION, PEPPER, SAUSAGE 199 | } 200 | 201 | final Set toppings; 202 | 203 | abstract static class Builder> { 204 | EnumSet toppings = EnumSet.noneOf(Topping.class); 205 | 206 | public T addTopping(Topping topping) { 207 | toppings.add(Objects.requireNonNull(topping)); 208 | return self(); 209 | } 210 | 211 | abstract Pizza build(); 212 | 213 | // 子类必须覆盖这个方法用来返回 “this” 214 | protected abstract T self(); 215 | } 216 | 217 | Pizza(Builder builder) { 218 | toppings = builder.toppings.clone(); // 参考第 50 条 219 | } 220 | } 221 | ``` 222 | 223 | 需要注意的是 `Pizza.Builder` 是一个带有递归类型参数的泛型类型([第 30 条][item30])。这与抽象的 self 方法一起,允许方法链在子类中工作,而不需要强制转换。对于 Java 缺乏自身类型(self type)的事实,这个解决方法被认为是模拟自身类型的习惯用法。 224 | 225 | 这里有两个 Pizza 类的具体子类,其中一个代表标准的纽约风格(New-York-style)披萨,另一个则是半圆形烤馅饼(calzone)。前者有一个必需的尺寸参数,而后者则可以让你指定酱汁应该在外面还是里面: 226 | 227 | ```java 228 | public class NyPizza extends Pizza { 229 | public enum Size {SMALL, MEDIUM, LARGE} 230 | 231 | private final Size size; 232 | 233 | public static class Builder extends Pizza.Builder { 234 | private final Size size; 235 | 236 | public Builder(Size size) { 237 | this.size = Objects.requireNonNull(size); 238 | } 239 | 240 | @Override 241 | public NyPizza build() { 242 | return new NyPizza(this); 243 | } 244 | 245 | @Override 246 | protected Builder self() { 247 | return this; 248 | } 249 | } 250 | 251 | private NyPizza(Builder builder) { 252 | super(builder); 253 | size = builder.size; 254 | } 255 | } 256 | 257 | public class Calzone extends Pizza { 258 | private final boolean sauceInside; 259 | 260 | public static class Builder extends Pizza.Builder { 261 | private boolean sauceInside = false; // 默认 262 | 263 | public Builder sauceInside() { 264 | sauceInside = true; 265 | return this; 266 | } 267 | 268 | @Override 269 | public Calzone build() { 270 | return new Calzone(this); 271 | } 272 | 273 | @Override 274 | protected Builder self() { 275 | return this; 276 | } 277 | } 278 | 279 | private Calzone(Builder builder) { 280 | super(builder); 281 | sauceInside = builder.sauceInside; 282 | } 283 | } 284 | ``` 285 | 286 | 值得注意的是,每个子类的 builder 类的 build 方法被声明为返回正确的子类:NyPizza.Builder 的 build 方法返回 NyPizza 对象,而在 Calzone.Builder 中的 builder 方法则是返回 Calzone 对象。这种声明子类方法返回超类中声明的返回类型的子类型的技术,称为协变返回类型(covariant return typing)。它允许客户端使用这些 builder 而不需要强制转换。 287 | 288 | 这些 “层级 builder” 的客户端代码基本上与简易的 NutritionFacts 的 builder 代码相同。为简洁起见,下面展示的示例客户端代码假定枚举常量静态导入: 289 | 290 | ```java 291 | NyPizza pizza = new NyPizza.Builder(SMALL) 292 | .addTopping(SAUSAGE) 293 | .addTopping(ONION) 294 | .build(); 295 | 296 | Calzone calzone = new Calzone.Builder() 297 | .addTopping(HAM) 298 | .sauceInside() 299 | .build(); 300 | ``` 301 | 302 | Builder 相对于构造器一个略微优势在于 builder 可以有多个可变参数(varargs),因为每个参数都可以在其自己的方法中被指定。或者,builder 可以将传递给方法的多个调用的参数聚合到单个字段中,如前面的 addTopping 方法所示。 303 | 304 | Builder 模式相当的灵活,单个 builder 可以重复用来创建多个对象。可以在 build 方法的调用间调整 builder 的参数,以改变创建的对象。Builder 可以在创建对象时自动填充某些字段,例如每次创建对象时增加的序列号。 305 | 306 | Builder 模式同样有缺陷。为了创建一个对象,你必须先创建它的 builder。尽管在实践中~~并~~不太可能注意到创建 builder 的成本,但在性能关键的情况下会成为一个问题。此外,Builder 模式比重叠构造器模式更加的冗长,因此只有在有足够的参数(例如四个或更多)使其值得使用时才应使用它。但请记住,将来你可能添加更多的参数。但如果一开始使用构造器或者静态工厂,当类发展到参数数量失控时才切换到 Builder 模式,那么过时的构造器或者静态工厂将会显得十分突兀。因此,通常最好一开始就是用 builder 。 307 | 308 | 总之,**当待设计类的构造器或静态工厂具有多个参数时,Builder 模式会是个好的选择**,特别是如果很多参数是可选的或是类型相同的。带有 builder 的客户端代码比带有重叠构造器的代码更容易阅读和编写,并且 builder 比 JavaBeans 要安全得多。 309 | 310 |

[Gamma95] Gamma, Erich, RichardHelm, Ralph Johnson, and John Vlissides. 1995. Design Patterns: Elements of Reusable Object-Oriented Software. Reading, MA: Addison-Wesley. ISBN: 0201633612.

311 | 312 | [item17]: url "在未来填入第 17 条的 url,否则无法跳转" 313 | [item24]: url "在未来填入第 24 条的 url,否则无法跳转" 314 | [item50]: url "在未来填入第 50 条的 url,否则无法跳转" 315 | [item72]: url "在未来填入第 72 条的 url,否则无法跳转" 316 | [item75]: url "在未来填入第 75 条的 url,否则无法跳转" 317 | [item30]: url "在未来填入第 75 条的 url,否则无法跳转" 318 | 319 | --- 320 | 321 | > 翻译:Inno 322 | > 323 | > 校对:Angus 324 | -------------------------------------------------------------------------------- /2.创建和销毁对象/第 3 条:用私有构造器或者枚举类型强化 Singleton 属性.md: -------------------------------------------------------------------------------- 1 | # 第 3 条:用私有构造器或者枚举类型强化 Singleton 属性 2 | 3 | 单例(singleton)指仅仅被实例化一次的类 [[Gamma95](#Gamma95)]。单例对象通常表示无状态对象,如函数(function [第 24 条](item24))或本质上唯一的系统组件。**使类成为单例会使测试它的客户端变得困难**,因为不可能用模拟实现代替单例,除非它实现一个充当其类型的接口。 4 | 5 | 实现单例的方法有两种。这两者都基于保持构造器私有并导出公有静态成员以提供对唯一实例访问的方式。在第一种方法中,公有静态成员是个 final 域: 6 | 7 | ```java 8 | // 公有域方法实现 singleton 9 | public class Elvis { 10 | public static final Elvis INSTANCE = new Elvis(); 11 | private Elvis() { ... } 12 | public void leaveTheBuilding() { ... } 13 | } 14 | ``` 15 | 16 | 私有构造器只会被调用一次,以初始化公有静态 final 域 `Elvis.INSTANCE`。缺少公有或受保护的构造器可以保证全局唯一性:一旦 `Elvis` 类被初始化,`Elvis` 实例只会存在一个——不多也不少。客户端所做的任何事情都无法改变这一点,但要提醒一下:享有特权的客户端可以借助 `AccessibleObject.setAccessible` 方法,通过反射机制([第 65 条][item65])调用私有构造器。如果需要防范这种攻击,可以修改构造器,让它在被要求创建第二个实例时抛出异常。 17 | 18 | 在实现单例的第二种方法中,公有成员是个静态工厂方法: 19 | 20 | ```java 21 | // 静态工厂方法实现 singleton 22 | public class Elvis { 23 | private static final Elvis INSTANCE = new Elvis(); 24 | private Elvis() { ... } 25 | public static Elvis getInstance() { return INSTANCE; } 26 | 27 | public void leaveTheBuilding() { ... } 28 | 29 | } 30 | ``` 31 | 32 | 对 `Elvis.getInstance` 的所有调用都会返回相同的对象引用,不会创建其他的 `Elvis` 实例(上述的提醒仍然适用)。 33 | 34 | 公有域方法的主要优点是 API 能清楚地表明该类是单例:公有静态域是 final 的,因此它始终包含相同的对象引用。第二个优点是它更简单。 35 | 36 | 静态工厂方法的优势之一在于,它提供了灵活性,在不更改 API 的情况下,你可以改变类是否应该是单例的想法。工厂方法返回唯一的实例,但可以对它进行修改,例如,改成为每个调用它的线程返回一个单独的实例。第二个优势是,如果应用需要,你可以编写通用的单例工厂(generic singleton factory [第 30 条](item30)。使用静态工厂的最后一个优点是,方法引用(method reference)可以被当作 supplier 来使用,例如 `Elvis::instance` 即是一个 `Supplier`。除非与其中一个优点相关,否则公有域方法更可取。 37 | 38 | 为了使利用其中任意一种方法实现的单例类是可序列化的(serializable [第 12 章](chapter12)),仅仅在其声明添加 `implements Serializable` 是不够的。要维护并保证单例,需要声明所有实例字段为 `transient` 并提供 `readResolve` 方法([第 89 条](item89))。否则,每次一个已序列化的实例被反序列化时,都会创建一个新实例。比如,在我们的示例中,将导致出现 “假冒的 `Elvis`”。为了防止这种情况发生,需要在 `Elvis` 类中加入下面这个 `readResolve` 方法: 39 | 40 | ```java 41 | // readResolve 方法保护 singleton 性质 42 | private Object readResolve() { 43 | // 返回真正的 Elvis, 44 | // 让垃圾回收器处理 Elvis 的模仿者 45 | return INSTANCE; 46 | } 47 | ``` 48 | 49 | 实现单例的第三种方法是声明一个单元素的枚举类(single-element enum): 50 | 51 | ```java 52 | // Enum singleton - 首选的方法 53 | public enum Elvis { 54 | INSTANCE; 55 | public void leaveTheBuilding() { ... } 56 | } 57 | ``` 58 | 59 | 这种方法类似于公有域方法,但它更简洁,并且无偿提供了序列化机制,即使面对复杂的序列化或反射攻击,也能提供防止多实例化的严格保障。这种方法可能会让人感觉有点不自然,但**单元素的枚举类型通常是实现单例的最佳方法**。请注意,如果你的单例必须继承除 `Enum` 之外的超类,就不能使用该方法(即使可以声明一个枚举来实现接口)。 60 | 61 |

[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.

62 | 63 | [item24]: url "在未来填入第 24 条的 url,否则无法跳转" 64 | [item30]: url "在未来填入第 30 条的 url,否则无法跳转" 65 | [item65]: url "在未来填入第 65 条的 url,否则无法跳转" 66 | [item89]: url "在未来填入第 89 条的 url,否则无法跳转" 67 | [chapter12]: url "在未来填入第 12 张的 url,否则无法跳转" 68 | 69 | > 翻译:Angus 70 | > 71 | > 校对:Inno 72 | -------------------------------------------------------------------------------- /2.创建和销毁对象/第 4 条:通过私有构造器强化不可实例化能力.md: -------------------------------------------------------------------------------- 1 | # 第 4 条:通过私有构造器强化不可实例化的能力 2 | 3 | 有时候,你可能需要编写只包含静态方法和静态属性的类。这些类名声很不好,因为有些人在面向对象的语言中滥用这样的类来编写过程化的程序,但是它们确实有特有的用处。它们可被用来以 java.lang.Math 或者 java.utill.Arrays 的方式,把初始值或数组的相关方法进行组合。它们也可被用来以 java.util.Collections 的方式,把实现某些接口对象的静态方法(包括静态工厂方法([第 1 条][item1]))进行组合(自 Java 8 开始,你也可以把这些方法写进你定义的接口中)。最后,这些类还可以用来把 final 类中的方法进行组合,因为你不可能把这些方法放到一个子类中。 4 | 5 | 这样的工具类(*utility class*)不需要被实例化,它的实例是没有意义的。 然而,当缺乏显式构造器时,编译器会提供一个公有的、无参的默认构造器(*default constructor*)。对用户而言,这个构造器和其它构造器是没有任何区别的。在已发布的 API 中,经常可以看到一些被无意识地实例化的类。 6 | 7 | **企图通过把类做成抽象来强制不可实例化是行不通的**。 该类可以被子类化,并且其子类可以被实例化。除此之外,它还会误导读者认为该类是为继承而设计的([第 19 条][item19])。其实,这有一个简单的习惯用法来确保类的不可实例化。只有当一个类不包含显式构造器时,才会生成一个默认的构造器。所以**通过让类包含私有构造器,可以使其不可实例化**: 8 | 9 | ```java 10 | // 不可实例化工具类 11 | public class UtilityClass { 12 | // 禁止默认构造器,以实现非实例化 13 | private UtilityClass() { 14 | throw new AssertionError(); 15 | } 16 | // .. 17 | // 其余省略 18 | } 19 | ``` 20 | 21 | 因为显式构造器是私有的,所以在类外是不可访问的。AssertionError 并不是严格需要的,但它可以防止在类内意外调用构造器。它可以保证类在任何环境下都绝不可能被实例化。这个习惯用法有点违反直觉,因为构造器是明确提供的却是为了该构造器无法被调用,因此,如上面所示那样,添加一条注释不失为一个明了的写法。 22 | 23 | 这个习惯用法也有一些副作用,它同时也阻止了该类被子类化。因为该类中所有的构造器必须显式或隐式地调用一个超类(superclass)构造器,在这种情况下,子类就没有可以访问的超类构造器来调用了。 24 | 25 | [item1]: ./第%201%20条:考虑用静态工厂方法代替构造器.md "第 1 条:考虑用静态工厂方法代替构造器" 26 | [item19]:url "在未来填入第 19 条的 url,否则无法进行跳转" 27 | 28 | --- 29 | 30 | > 翻译:Inno 31 | > 32 | > 校对:Inger 33 | -------------------------------------------------------------------------------- /2.创建和销毁对象/第 5 条:依赖注入优于资源硬连接.md: -------------------------------------------------------------------------------- 1 | # 第 5 条:依赖注入(dependency injection)优于资源硬连接(hardwiring resources) 2 | 3 | 很多类依赖于一个或多个底层资源(underlying resources)。比如,一个拼写检查器依赖一本词典。这种类被实现为静态工具类的情况并不少见([第 4 条][item4]): 4 | 5 | ```JAVA 6 | // 静态工具类的不恰当使用 - 不灵活 & 不可测试! 7 | public class SpellChecker { 8 | private static final Lexicon dictionary = ...; 9 | 10 | private SpellChecker() {} // 不可实例化 11 | 12 | public static boolean isValid(String word) { ... } 13 | public static List suggestions(String typo) { ... } 14 | } 15 | ``` 16 | 17 | 同样,它们被实现为单例的情况也很常见: 18 | 19 | ```JAVA 20 | // 单例的不恰当使用 - 不灵活 & 不可测试! 21 | public class SpellChecker { 22 | private final Lexicon dictionary = ...; 23 | 24 | private SpellChecker(...) {} 25 | public static INSTANCE = new SpellChecker(...); 26 | 27 | public boolean isValid(String word) { ... } 28 | public List suggestions(String typo) { ... } 29 | } 30 | ``` 31 | 32 | 这两种方式都不尽人意,因为他们假设只有一本字典会被使用。事实上,每种语言都有它自己的字典,并且特定的词汇需要特定的字典。同样,用一本特殊的字典来测试也是我们所期望的。妄想使用一本字典满足所有的需求只是一厢情愿而已。 33 | 34 | 为了使 `SpellChecker` 支持多本字典,你可能会想到通过去掉 `dictionary` 的 `final` 属性,并添加一个改变现有 `SpellChecker` 中 `dictionary` 属性的方法来实现,然而在并发环境下,这种方式显得笨拙,更容易出错导致程序终止。**行为被底层资源参数化的类不适合作为静态工具类或单例**。 35 | 36 | 我们需要的是类具有支持多个实例的能力(在我们的例子中是 `SpellChcker`),并且在每个实例中使用客户端所需的资源(在我们例子中是 `dictionary`)。满足这一需求的简单方法是在**创建新的实例时,将资源传递到构造器中**。这是依赖注入(*dependency injection*)的一种形式:拼写检查器所依赖的字典在 `SpellChecker` 对象被创建时注入到其构造方法中。 37 | 38 | ```java 39 | // 依赖注入提供了灵活性和可测性 40 | public class SpellChecker { 41 | private final Lexicon dictionary; 42 | 43 | public SpellChecker(Lexicon dictionary) { 44 | this.dictionary = Objects.requireNonNull(dictionary); 45 | } 46 | 47 | public boolean isValid(String word) { ... } 48 | public List suggestions(String typo) { ... } 49 | } 50 | ``` 51 | 52 | 依赖注入模式如此简单,以至于很多程序员常年都在使用却不知道它还有个名字。虽然我们的拼写检查器的例子只依赖于一个资源(`dictionary`),实际上依赖注入可以支持任意数量的资源和依赖图(dependency graph)。它保证了底层资源的不可变性([第 17 条][item17]),所以多个客户端可以共享依赖对象(假如客户端申请同一个底层资源)。依赖注入同样适用于构造器,静态工厂和构建器([第 2 条][item2])~~等~~。 53 | 54 | 依赖注入模式的一个实用变体是给构造器传递一个资源工厂(resource factory),即创建类型实例时可以被反复调用的对象。像这样的工厂体现了工厂方法模式(factory method pattern)。JAVA 8 中引入的 `Supplier` 接口非常适合表示工厂。当一个方法使用 `Supplier` 作为输入参数时,应该通过使用边界通配符类型(bound wildcard type [第 31 条][item31])限制其工厂的类型参数,这样客户端就可以传入创建指定类型的子类型的工厂。比如下面这个例子,使用客户端提供的工厂来生产 tile (瓷砖),从而制作 mosaic(马赛克): 55 | 56 | ```java 57 | Mosaic create(Supplier 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 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 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 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 | [![](https://img.shields.io/badge/License-GPL--3.0-brightgreen.svg)](./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 | --------------------------------------------------------------------------------