From 98ddf35297698f69083af9bcec63ae3213c3c1d3 Mon Sep 17 00:00:00 2001 From: clxeringlab Date: Tue, 12 Feb 2019 09:29:23 +0800 Subject: [PATCH 01/18] update some templates in .github --- .github/CONTRIBUTING.md | 226 +++++++++++++++- .github/CONTRIBUTING_zh.md | 252 ++++++++++++++++++ .github/PULL_REQUEST_TEMPLATE.md | 8 +- ...h-a-private-constructor-or-an-enum-type.md | 2 +- 4 files changed, 478 insertions(+), 10 deletions(-) create mode 100644 .github/CONTRIBUTING_zh.md diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 3dfa0d4..036d5d3 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -1,14 +1,23 @@ # Contributing Guide +[切换到简体中文](CONTRIBUTING_zh.md) + I’m really excited that you are interested in contributing to this project. Before submitting your contribution though, please make sure to take a moment and read through the following guidelines. - [Code of Conduct](CODE_OF_CONDUCT.md) - [Issue Reporting Guidelines](#issue-reporting-guidelines) - [Pull Request Guidelines](#pull-request-guidelines) +- [Format](#Format) ## Issue Reporting Guidelines -- Always use an organized format or template to create new issues. +- Always use an template to create new issues. + +- If adding new discussion or suggestion: + - PLS use the `Discussion_or_Suggestion` template. + +- If finding a bug or adding new improvement: + - PLS use the `Bug_or_Improvement` template. ## Pull Request Guidelines @@ -16,8 +25,215 @@ I’m really excited that you are interested in contributing to this project. Be - Checkout a topic branch from the relevant branch, e.g. `dev`, and merge back against that branch. -- If adding new improvement: - - Provide convincing reason to add this improvement. Ideally you should open a suggestion issue first and have it greenlighted before working on it. +- Provide convincing reason to fixing a bug or add this improvement. Ideally you should open a issue first and have it greenlighted before working on it. + +- If you are resolving a issue, add `(closes #xxxx[,#xxx])` (#xxxx is the issue id) in your PR title for a better release log, e.g. `update Item 19 (closes #1899)`. + +- All updated content should be formatted in accordance with [Format](#Format) requirements. + +## Format +The following is a quote from: [中文文案排版指北](https://github.com/sparanoid/chinese-copywriting-guidelines) + +## Spacing + +### Place one space before/after English words + +Good: + +> 在 LeanCloud 上,数据存储是围绕 `AVObject` 进行的。 + +Bad: + +> 在LeanCloud上,数据存储是围绕`AVObject`进行的。 +> +> 在 LeanCloud上,数据存储是围绕`AVObject` 进行的。 + +An example of complete and correct usage: + +> 在 LeanCloud 上,数据存储是围绕 `AVObject` 进行的。 每个 `AVObject` 都包含了与 JSON 兼容的 key-value 对应的数据。 数据是 schema-free 的,你不需要在每个 `AVObject` 上提前指定存在哪些键,只要直接设定对应的 key-value 即可。 + +Exceptions: For product and brand names, please refer to the writing format of the official definition. For example, use “豆瓣FM” instead of “豆瓣 FM”. + +### Place one space before/after numbers + +Good: + +> 今天出去买菜花了 5000 元。 + +Bad: + +> 今天出去买菜花了 5000元。 +> +> 今天出去买菜花了5000元。 + +### Place one space between numbers and units + +Good: + +> 我家的光纤入屋宽频有 10 Gbps,SSD 一共有 20 TB + +Bad: + +> 我家的光纤入屋宽频有 10Gbps,SSD 一共有 20TB + +Exceptions: There should not be any spacing between numbers and degrees/percentages. + +Good: + +> 今天是 233° 的高温。 +> +> 新 MacBook Pro 有 15% 的 CPU 性能提升。 + +Bad: + +> 今天是 233 ° 的高温。 +> +> 新 MacBook Pro 有 15 % 的 CPU 性能提升。 + +### No additional spaces before/after punctuation in fullwidth form + +Good: + +> 刚刚买了一部 iPhone,好开心! + +Bad: + +> 刚刚买了一部 iPhone ,好开心! +> +> 刚刚买了一部 iPhone, 好开心! + +## Punctuation + +### Avoid duplicate punctuation + +Good: + +> 德国队竟然战胜了巴西队! +> +> 她竟然对你说「喵」?! + +Bad: + +> 德国队竟然战胜了巴西队!! +> +> 德国队竟然战胜了巴西队!!!!!!!! +> +> 她竟然对你说「喵」??!! +> +> 她竟然对你说「喵」?!?!??!! + +## Fullwidth and halfwidth + +If you’re not familiar with fullwidth and halfwidth forms please refer to [Halfwidth and fullwidth](http://zh.wikipedia.org/wiki/%E5%85%A8%E5%BD%A2%E5%92%8C%E5%8D%8A%E5%BD%A2) forms on Wikipedia. + +### Use punctuation in fullwidth form + +Good: + +> 嗨!你知道嘛?今天前台的小妹跟我说「喵」了哎! +> +> 核磁共振成像(NMRI)是什么原理都不知道?JFGI! + +Bad: + +> 嗨! 你知道嘛? 今天前台的小妹跟我说 "喵" 了哎! +> +> 嗨!你知道嘛?今天前台的小妹跟我说"喵"了哎! +> +> 核磁共振成像 (NMRI) 是什么原理都不知道? JFGI! +> +> 核磁共振成像(NMRI)是什么原理都不知道?JFGI! + +### Use numbers in halfwidth form + +Good: + +> 这件蛋糕只卖 1000 元。 + +Bad: + +> 这件蛋糕只卖 1000 元。 + +Exceptions: fullwidth numbers are acceptable for better visual alignment in graphic design. + +### Use punctuation in halfwidth form for English sentences + +Good: + +> 贾伯斯那句话是怎么说的?「Stay hungry, stay foolish.」 +> +> 推荐你阅读《Hackers & Painters: Big Ideas from the Computer Age》,非常的有趣。 + +Bad: + +> 贾伯斯那句话是怎么说的?「Stay hungry,stay foolish。」 +> +> 推荐你阅读《Hackers&Painters:Big Ideas from the Computer Age》,非常的有趣。 + +## Nouns + +### Proper nouns are properly capitalized + +Uppercase and lowercase are used in English and are not discussed in this wiki. Here is a brief overview of the error-prone usage. + +Good: + +> 使用 GitHub 登录 +> +> 我们的客户有 GitHub、Foursquare、Microsoft Corporation、Google、Facebook, Inc.。 + +Bad: + +> 使用 github 登录 +> +> 使用 GITHUB 登录 +> +> 使用 Github 登录 +> +> 使用 gitHub 登录 +> +> 使用 gイんĤЦ8 登录 +> +> 我们的客户有 github、foursquare、microsoft corporation、google、facebook, inc.。 +> +> 我们的客户有 GITHUB、FOURSQUARE、MICROSOFT CORPORATION、GOOGLE、FACEBOOK, INC.。 +> +> 我们的客户有 Github、FourSquare、MicroSoft Corporation、Google、FaceBook, Inc.。 +> +> 我们的客户有 gitHub、fourSquare、microSoft Corporation、google、faceBook, Inc.。 +> +> 我们的客户有 gイんĤЦ8、キouЯƧquムгє、๓เςг๏ร๏Ŧt ς๏гק๏гคtเ๏ภn、900913、ƒ4ᄃëв๏๏к, IПᄃ.。 + +### Avoid jargons + +Good: + +> 我们需要一位熟悉 JavaScript、HTML5,至少理解一种框架(如 Backbone.js、AngularJS、React 等)的前端开发者。 + +Bad: + +> 我们需要一位熟悉 Js、h5,至少理解一种框架(如 backbone、angular、RJS 等)的 FED。 + +### Add extra spaces before/after links + +Good: + +> 请 [提交一个 issue](#) 并分配给相关同事。 +> +> 访问我们网站的最新动态,请 [点击这里](#) 进行订阅! + +Bad: + +> 请[提交一个 issue](#)并分配给相关同事。 +> +> 访问我们网站的最新动态,请[点击这里](#)进行订阅! + +### Use corner brackets for Chinese Simplified + +Good: + +> 「老师,『有条不紊』的『紊』是什么意思?」 + +Bad: -- If fixing a bug: - - If you are resolving a issue, add `(closes #xxxx[,#xxx])` (#xxxx is the issue id) in your PR title for a better release log, e.g. `update Item 19 (closes #1899)`. +> “老师,‘有条不紊’的‘紊’是什么意思?” diff --git a/.github/CONTRIBUTING_zh.md b/.github/CONTRIBUTING_zh.md new file mode 100644 index 0000000..7b2ec45 --- /dev/null +++ b/.github/CONTRIBUTING_zh.md @@ -0,0 +1,252 @@ +# 贡献指南 + +[Switch to English](CONTRIBUTING.md) + +非常感谢您有兴趣为这个翻译项目做贡献,我深感荣幸。在提交您的宝贵意见之前,请务必花点时间阅读以下指南: + +- [Code of Conduct](CODE_OF_CONDUCT.md) +- [问题提交指南](#问题提交指南) +- [Pull Request 指南](#Pull Request 指南) +- [格式](#格式) + +## 问题提交指南 + +- 使用模版来创建新的问题。 + +- 如果添加一些问题讨论或建议 + - 请使用 `Discussion_or_Suggestion` 模版。 + +- 如果发现了错误或认为译文很有必要改进 + - 请使用 `Bug_or_Improvement` 模版。 + +## Pull Request 指南 + +- `master` 分支用于保存最新版本。所有的操作都应该在专门的分支中进行。**不要将任何 PR 提交到 `master` 分支。** + +- 目前已经分离出一个主题分支,如:`dev`,所有的操作都在该分支进行,并对该分支进行合并。 + +- 修复错误或添加任何改进都应该描述理由和依据。理想情况下,您应该先创建一个问题,如果您希望亲自处理,则通过 pr 引用该问题。 + +- 如果您的 pr 用于解决列表中提出的一个或多个问题,请引用该问题 `(closes #xxxx[,#xxx])`,添加问题的序号,例如:`update Item 19 (closes #1899)` + +- 所有内容的更改应符合 [格式](#格式) 的要求。 + +## 格式 +以下内容引用自:[中文文案排版指北](https://github.com/sparanoid/chinese-copywriting-guidelines) + +## 空格 + +### 中英文之间需要增加空格 + +正确: + +> 在 LeanCloud 上,数据存储是围绕 `AVObject` 进行的。 + +错误: + +> 在LeanCloud上,数据存储是围绕`AVObject`进行的。 +> +> 在 LeanCloud上,数据存储是围绕`AVObject` 进行的。 + +完整的正确用法: + +> 在 LeanCloud 上,数据存储是围绕 `AVObject` 进行的。 每个 `AVObject` 都包含了与 JSON 兼容的 key-value 对应的数据。 数据是 schema-free 的,你不需要在每个 `AVObject` 上提前指定存在哪些键,只要直接设定对应的 key-value 即可。 + +例外:「豆瓣FM」等产品名词,按照官方所定义的格式书写。 + +### 中文与数字之间需要增加空格 + +正确: + +> 今天出去买菜花了 5000 元。 + +错误: + +> 今天出去买菜花了 5000元。 +> +> 今天出去买菜花了5000元。 + +### 数字与单位之间需要增加空格 + +正确: + +> 我家的光纤入屋宽频有 10 Gbps,SSD 一共有 20 TB + +错误: + +> 我家的光纤入屋宽频有 10Gbps,SSD 一共有 20TB + +例外:度 / 百分比与数字之间不需要增加空格: + +正确: + +> 今天是 233° 的高温。 +> +> 新 MacBook Pro 有 15% 的 CPU 性能提升。 + +错误: + +> 今天是 233 ° 的高温。 +> +> 新 MacBook Pro 有 15 % 的 CPU 性能提升。 + +### 全角标点与其他字符之间不加空格 + +正确: + +> 刚刚买了一部 iPhone,好开心! + +错误: + +> 刚刚买了一部 iPhone ,好开心! +> +> 刚刚买了一部 iPhone, 好开心! + +## 标点符号 + +### 不重复使用标点符号 + +正确: + +> 德国队竟然战胜了巴西队! +> +> 她竟然对你说「喵」?! + +错误: + +> 德国队竟然战胜了巴西队!! +> +> 德国队竟然战胜了巴西队!!!!!!!! +> +> 她竟然对你说「喵」??!! +> +> 她竟然对你说「喵」?!?!??!! + +## 全角和半角 + +不明白什么是全角(全形)与半角(半形)符号?请查看维基百科词条『[全形和半形](http://zh.wikipedia.org/wiki/%E5%85%A8%E5%BD%A2%E5%92%8C%E5%8D%8A%E5%BD%A2)』。 + +### 使用全角中文标点 + +正确: + +> 嗨!你知道嘛?今天前台的小妹跟我说「喵」了哎! +> +> 核磁共振成像(NMRI)是什么原理都不知道?JFGI! + +错误: + +> 嗨! 你知道嘛? 今天前台的小妹跟我说 "喵" 了哎! +> +> 嗨!你知道嘛?今天前台的小妹跟我说"喵"了哎! +> +> 核磁共振成像 (NMRI) 是什么原理都不知道? JFGI! +> +> 核磁共振成像(NMRI)是什么原理都不知道?JFGI! + +### 数字使用半角字符 + +正确: + +> 这件蛋糕只卖 1000 元。 + +错误: + +> 这件蛋糕只卖 1000 元。 + +例外:在设计稿、宣传海报中如出现极少量数字的情形时,为方便文字对齐,是可以使用全形数字的。 + +### 遇到完整的英文整句、特殊名词,其内容使用半角标点 + +正确: + +> 贾伯斯那句话是怎么说的?「Stay hungry, stay foolish.」 +> +> 推荐你阅读《Hackers & Painters: Big Ideas from the Computer Age》,非常的有趣。 + +错误: + +> 贾伯斯那句话是怎么说的?「Stay hungry,stay foolish。」 +> +> 推荐你阅读《Hackers&Painters:Big Ideas from the Computer Age》,非常的有趣。 + +## 名词 + +### 专有名词使用正确的大小写 + +大小写相关用法原属于英文书写范畴,不属于本 wiki 讨论内容,在这里只对部分易错用法进行简述。 + +正确: + +> 使用 GitHub 登录 +> +> 我们的客户有 GitHub、Foursquare、Microsoft Corporation、Google、Facebook, Inc.。 + +错误: + +> 使用 github 登录 +> +> 使用 GITHUB 登录 +> +> 使用 Github 登录 +> +> 使用 gitHub 登录 +> +> 使用 gイんĤЦ8 登录 +> +> 我们的客户有 github、foursquare、microsoft corporation、google、facebook, inc.。 +> +> 我们的客户有 GITHUB、FOURSQUARE、MICROSOFT CORPORATION、GOOGLE、FACEBOOK, INC.。 +> +> 我们的客户有 Github、FourSquare、MicroSoft Corporation、Google、FaceBook, Inc.。 +> +> 我们的客户有 gitHub、fourSquare、microSoft Corporation、google、faceBook, Inc.。 +> +> 我们的客户有 gイんĤЦ8、キouЯƧquムгє、๓เςг๏ร๏Ŧt ς๏гק๏гคtเ๏ภn、900913、ƒ4ᄃëв๏๏к, IПᄃ.。 + +注意:当网页中需要配合整体视觉风格而出现全部大写/小写的情形,HTML 中请使用标淮的大小写规范进行书写;并通过 `text-transform: uppercase;`/`text-transform: lowercase;` 对表现形式进行定义。 + +### 不要使用不地道的缩写 + +正确: + +> 我们需要一位熟悉 JavaScript、HTML5,至少理解一种框架(如 Backbone.js、AngularJS、React 等)的前端开发者。 + +错误: + +> 我们需要一位熟悉 Js、h5,至少理解一种框架(如 backbone、angular、RJS 等)的 FED。 + +### 链接之间增加空格 + +正确: + +> 请 [提交一个 issue](#) 并分配给相关同事。 +> +> 访问我们网站的最新动态,请 [点击这里](#) 进行订阅! + +错误: + +> 请[提交一个 issue](#)并分配给相关同事。 +> +> 访问我们网站的最新动态,请[点击这里](#)进行订阅! + +### 简体中文使用直角引号 + +正确: + +> 「老师,『有条不紊』的『紊』是什么意思?」 + +错误: + +> “老师,‘有条不紊’的‘紊’是什么意思?” + +## 其他注意事项 + +### 泛型的正确标注 +由于 markdown 支持 html 标签,泛型的尖括号如果直接使用,会误判为 html 标签,导致解析为正文时无法显示。因此,应将泛型放到单行代码(\` \`)中。正确的使用,如:`Comparable` + +### 代码或方法的正确标注 +原文在正文中提及的代码在译文中保持半角标点符号,并放到单行代码中。如:`sgn(x.compareTo(y)) == -sgn(y. compareTo(x))`、`BigDecimal("1.0")`,等等。 + +### 代码缩进 +代码块缩进采用 4 个空白。 diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 9f219a4..9507f2c 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,13 +1,13 @@ -**When resolving a issue, it's referenced in follow:(当解决一个 issue 时,引用该 issue 的编号):** +**When resolving a issue, it's referenced in follow:** (当解决一个 issue 时,引用该 issue 的编号) closes # -**What kind of change does this PR introduce?(该 PR 带来了什么样的改变,至少选择一个)** (check at least one) +**What kind of change does this PR introduce? (check at least one.)** (该 PR 带来了什么样的改变,至少选择一个) @@ -15,8 +15,8 @@ closes # - [ ] Improvement(改进翻译质量) - [ ] Other, please describe(其他): -**The PR fulfills these requirements(PR 应该符合的要求):** +**The PR fulfills these requirements:** (该 PR 应该符合的要求) -- [ ] It's submitted to the `dev` branch, not the master branch.(PR 应该提交到 `dev` 分支,而不是 master 分支) +- [ ] It's submitted to the `dev` branch, not the `master` branch.(该 PR 应该提交到 `dev` 分支,而不是 `master` 分支) **Other information(其他信息):** diff --git a/Chapter-2/Chapter-2-Item-3-Enforce-the-singleton-property-with-a-private-constructor-or-an-enum-type.md b/Chapter-2/Chapter-2-Item-3-Enforce-the-singleton-property-with-a-private-constructor-or-an-enum-type.md index 4e88063..a2b1514 100644 --- a/Chapter-2/Chapter-2-Item-3-Enforce-the-singleton-property-with-a-private-constructor-or-an-enum-type.md +++ b/Chapter-2/Chapter-2-Item-3-Enforce-the-singleton-property-with-a-private-constructor-or-an-enum-type.md @@ -77,7 +77,7 @@ obj.leaveTheBuilding(); To make a singleton class that uses either of these approaches serializable (Chapter 12), it is not sufficient merely to add implements Serializable to its declaration. To maintain(vt.维持) the singleton guarantee(n.保证), declare all instance fields transient and provide a readResolve method (Item 89). Otherwise, each time a serialized instance is deserialized, a new instance will be created, leading,in the case of our example, to spurious(adj.虚假的) Elvis sightings. To prevent this from happening, add this readResolve method to the Elvis class: -要使单例类使用这两种方法中的任何一种([12-Serialization]( Chapter-12/Chapter-12-Introduction.md)),仅仅在其声明中添加实现 serializable 是不够的。要维护单例保证,应声明所有实例字段为 transient,并提供 readResolve 方法([Item-89](https://github.com/clxering/Effective-Java-3rd-edition-Chinese-English-bilingual/blob/master/Chapter-12/Chapter-12-Item-89-For-instance-control-prefer-enum-types-to-readResolve.md))。否则,每次反序列化实例时,都会创建一个新实例,在我们的示例中,这会导致出现虚假的 Elvis。为了防止这种情况发生,将这个 readResolve 方法添加到 Elvis 类中: +要使单例类使用这两种方法中的任何一种(Chapter 12),仅仅在其声明中添加实现 serializable 是不够的。要维护单例保证,应声明所有实例字段为 transient,并提供 readResolve 方法([Item-89](https://github.com/clxering/Effective-Java-3rd-edition-Chinese-English-bilingual/blob/master/Chapter-12/Chapter-12-Item-89-For-instance-control-prefer-enum-types-to-readResolve.md))。否则,每次反序列化实例时,都会创建一个新实例,在我们的示例中,这会导致出现虚假的 Elvis。为了防止这种情况发生,将这个 readResolve 方法添加到 Elvis 类中: ``` // readResolve method to preserve singleton property From f0a09ae68ddadc642b7753e0fe11c98bb1192596 Mon Sep 17 00:00:00 2001 From: clxeringlab Date: Tue, 12 Feb 2019 11:55:22 +0800 Subject: [PATCH 02/18] Fixed anchor point problem. Bulk modify the format. --- .github/CONTRIBUTING.md | 6 +- .github/CONTRIBUTING_zh.md | 49 ++++++------ ...-2-Item-8-Avoid-finalizers-and-cleaners.md | 2 +- ...rride-hashCode-when-you-override-equals.md | 4 +- ...pter-3-Item-12-Always-override-toString.md | 4 +- ...er-3-Item-13-Override-clone-judiciously.md | 12 +-- ...he-accessibility-of-classes-and-members.md | 29 ++++--- ...-use-accessor-methods-not-public-fields.md | 15 ++-- .../Chapter-4-Item-17-Minimize-mutability.md | 36 ++++----- ...m-18-Favor-composition-over-inheritance.md | 22 ++--- ...ent-for-inheritance-or-else-prohibit-it.md | 34 ++++---- ...0-Prefer-interfaces-to-abstract-classes.md | 80 +++++++++---------- ...Item-21-Design-interfaces-for-posterity.md | 10 +-- ...-22-Use-interfaces-only-to-define-types.md | 36 ++++----- ...fer-class-hierarchies-to-tagged-classes.md | 6 +- ...or-static-member-classes-over-nonstatic.md | 22 ++--- ...ource-files-to-a-single-top-level-class.md | 44 +++++----- .../Chapter-5-Item-26-Do-not-use-raw-types.md | 8 +- ...hapter-5-Item-28-Prefer-lists-to-arrays.md | 4 +- .../Chapter-5-Item-29-Favor-generic-types.md | 6 +- ...d-wildcards-to-increase-API-flexibility.md | 4 +- ...m-34-Use-enums-instead-of-int-constants.md | 2 +- ...Use-instance-fields-instead-of-ordinals.md | 2 +- 23 files changed, 221 insertions(+), 216 deletions(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 036d5d3..349ea93 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -7,7 +7,7 @@ I’m really excited that you are interested in contributing to this project. Be - [Code of Conduct](CODE_OF_CONDUCT.md) - [Issue Reporting Guidelines](#issue-reporting-guidelines) - [Pull Request Guidelines](#pull-request-guidelines) -- [Format](#Format) +- [Format Guidelines](#format-guidelines) ## Issue Reporting Guidelines @@ -29,9 +29,9 @@ I’m really excited that you are interested in contributing to this project. Be - If you are resolving a issue, add `(closes #xxxx[,#xxx])` (#xxxx is the issue id) in your PR title for a better release log, e.g. `update Item 19 (closes #1899)`. -- All updated content should be formatted in accordance with [Format](#Format) requirements. +- All updated content should be formatted in accordance with [Format Guidelines](#format-guidelines) requirements. -## Format +## Format Guidelines The following is a quote from: [中文文案排版指北](https://github.com/sparanoid/chinese-copywriting-guidelines) ## Spacing diff --git a/.github/CONTRIBUTING_zh.md b/.github/CONTRIBUTING_zh.md index 7b2ec45..eb4584f 100644 --- a/.github/CONTRIBUTING_zh.md +++ b/.github/CONTRIBUTING_zh.md @@ -6,8 +6,8 @@ - [Code of Conduct](CODE_OF_CONDUCT.md) - [问题提交指南](#问题提交指南) -- [Pull Request 指南](#Pull Request 指南) -- [格式](#格式) +- [Pull Request 指南](#pull-request-指南) +- [格式指南](#格式指南) ## 问题提交指南 @@ -29,14 +29,15 @@ - 如果您的 pr 用于解决列表中提出的一个或多个问题,请引用该问题 `(closes #xxxx[,#xxx])`,添加问题的序号,例如:`update Item 19 (closes #1899)` -- 所有内容的更改应符合 [格式](#格式) 的要求。 +- 所有内容的更改应符合 [格式指南](#格式指南) 的要求。 + +## 格式指南 -## 格式 以下内容引用自:[中文文案排版指北](https://github.com/sparanoid/chinese-copywriting-guidelines) -## 空格 +### 空格 -### 中英文之间需要增加空格 +#### 中英文之间需要增加空格 正确: @@ -54,7 +55,7 @@ 例外:「豆瓣FM」等产品名词,按照官方所定义的格式书写。 -### 中文与数字之间需要增加空格 +#### 中文与数字之间需要增加空格 正确: @@ -66,7 +67,7 @@ > > 今天出去买菜花了5000元。 -### 数字与单位之间需要增加空格 +#### 数字与单位之间需要增加空格 正确: @@ -90,7 +91,7 @@ > > 新 MacBook Pro 有 15 % 的 CPU 性能提升。 -### 全角标点与其他字符之间不加空格 +#### 全角标点与其他字符之间不加空格 正确: @@ -102,9 +103,9 @@ > > 刚刚买了一部 iPhone, 好开心! -## 标点符号 +### 标点符号 -### 不重复使用标点符号 +#### 不重复使用标点符号 正确: @@ -122,11 +123,11 @@ > > 她竟然对你说「喵」?!?!??!! -## 全角和半角 +### 全角和半角 不明白什么是全角(全形)与半角(半形)符号?请查看维基百科词条『[全形和半形](http://zh.wikipedia.org/wiki/%E5%85%A8%E5%BD%A2%E5%92%8C%E5%8D%8A%E5%BD%A2)』。 -### 使用全角中文标点 +#### 使用全角中文标点 正确: @@ -144,7 +145,7 @@ > > 核磁共振成像(NMRI)是什么原理都不知道?JFGI! -### 数字使用半角字符 +#### 数字使用半角字符 正确: @@ -156,7 +157,7 @@ 例外:在设计稿、宣传海报中如出现极少量数字的情形时,为方便文字对齐,是可以使用全形数字的。 -### 遇到完整的英文整句、特殊名词,其内容使用半角标点 +#### 遇到完整的英文整句、特殊名词,其内容使用半角标点 正确: @@ -170,9 +171,9 @@ > > 推荐你阅读《Hackers&Painters:Big Ideas from the Computer Age》,非常的有趣。 -## 名词 +### 名词 -### 专有名词使用正确的大小写 +#### 专有名词使用正确的大小写 大小写相关用法原属于英文书写范畴,不属于本 wiki 讨论内容,在这里只对部分易错用法进行简述。 @@ -206,7 +207,7 @@ 注意:当网页中需要配合整体视觉风格而出现全部大写/小写的情形,HTML 中请使用标淮的大小写规范进行书写;并通过 `text-transform: uppercase;`/`text-transform: lowercase;` 对表现形式进行定义。 -### 不要使用不地道的缩写 +#### 不要使用不地道的缩写 正确: @@ -216,7 +217,7 @@ > 我们需要一位熟悉 Js、h5,至少理解一种框架(如 backbone、angular、RJS 等)的 FED。 -### 链接之间增加空格 +#### 链接之间增加空格 正确: @@ -230,7 +231,7 @@ > > 访问我们网站的最新动态,请[点击这里](#)进行订阅! -### 简体中文使用直角引号 +#### 简体中文使用直角引号 正确: @@ -240,13 +241,13 @@ > “老师,‘有条不紊’的‘紊’是什么意思?” -## 其他注意事项 +### 其他注意事项 -### 泛型的正确标注 +#### 泛型的正确标注 由于 markdown 支持 html 标签,泛型的尖括号如果直接使用,会误判为 html 标签,导致解析为正文时无法显示。因此,应将泛型放到单行代码(\` \`)中。正确的使用,如:`Comparable` -### 代码或方法的正确标注 +#### 代码或方法的正确标注 原文在正文中提及的代码在译文中保持半角标点符号,并放到单行代码中。如:`sgn(x.compareTo(y)) == -sgn(y. compareTo(x))`、`BigDecimal("1.0")`,等等。 -### 代码缩进 +#### 代码缩进 代码块缩进采用 4 个空白。 diff --git a/Chapter-2/Chapter-2-Item-8-Avoid-finalizers-and-cleaners.md b/Chapter-2/Chapter-2-Item-8-Avoid-finalizers-and-cleaners.md index e510556..9f8825d 100644 --- a/Chapter-2/Chapter-2-Item-8-Avoid-finalizers-and-cleaners.md +++ b/Chapter-2/Chapter-2-Item-8-Avoid-finalizers-and-cleaners.md @@ -135,4 +135,4 @@ public class Teenager { You might expect it to print Peace out, followed by Cleaning room, but on my machine, it never prints Cleaning room; it just exits. This is the unpredictability we spoke of earlier. The Cleaner spec says, “The behavior of cleaners during System.exit is implementation specific. No guarantees are made relating to whether cleaning actions are invoked or not.” While the spec does not say it, the same holds true for normal program exit. On my machine,adding the line System.gc() to Teenager’s main method is enough to make it print Cleaning room prior to exit, but there’s no guarantee that you’ll see the same behavior on your machine.In summary, don’t use cleaners, or in releases prior to Java 9, finalizers,except as a safety net or to terminate noncritical native resources. Even then,beware the indeterminacy and performance consequences. -你可能期望它打印出「Peace out」,然后打扫房间,但在我的机器上,它从不打扫房间;它只是退出。这就是我们之前提到的不可预测性。Cleaner 规范说:「在 System.exit 中,清洁器的行为是特定于实现的。不保证清理操作是否被调用。」虽然规范没有说明,但对于普通程序退出来说也是一样。在我的机器上,将 System.gc() 添加到 Teenager 的主要方法中就足以让它在退出之前打扫房间,但不能保证在其他机器上看到相同的行为。总之,不要使用清洁器,或者在 Java 9 之前的版本中使用终结器,除非是作为安全网或终止非关键的本机资源。即便如此,也要小心不确定性和性能后果。 +你可能期望它打印出「Peace out」,然后打扫房间,但在我的机器上,它从不打扫房间;它只是退出。这就是我们之前提到的不可预测性。Cleaner 规范说:「在 System.exit 中,清洁器的行为是特定于实现的。不保证清理操作是否被调用。」虽然规范没有说明,但对于普通程序退出来说也是一样。在我的机器上,将 System.gc() 添加到 Teenager 的主要方法中就足以让它在退出之前打扫房间,但不能保证在其他机器上看到相同的行为。总之,不要使用清洁器,或者在 Java 9 之前的版本中使用终结器,除非是作为安全网或终止非关键的本机资源。即便如此,也要小心不确定性和性能后果。 diff --git a/Chapter-3/Chapter-3-Item-11-Always-override-hashCode-when-you-override-equals.md b/Chapter-3/Chapter-3-Item-11-Always-override-hashCode-when-you-override-equals.md index c47c36f..c98ff80 100644 --- a/Chapter-3/Chapter-3-Item-11-Always-override-hashCode-when-you-override-equals.md +++ b/Chapter-3/Chapter-3-Item-11-Always-override-hashCode-when-you-override-equals.md @@ -114,11 +114,11 @@ public int hashCode() { Because this method returns the result of a simple deterministic(adj.确定性的) computation whose only inputs are the three significant fields in a PhoneNumber instance,it is clear that equal PhoneNumber instances have equal hash codes. This method is, in fact, a perfectly good hashCode implementation for PhoneNumber, on par with those in the Java platform libraries. It is simple, is reasonably fast, and does a reasonable job of dispersing unequal phone numbers into different hash buckets. -因为这个方法返回一个简单的确定性计算的结果,它的唯一输入是 PhoneNumber 实例中的三个重要字段,所以很明显,相等的 PhoneNumber 实例具有相等的 hash 码。实际上,这个方法是 PhoneNumber 的一个非常好的 hashCode 实现,与 Java 平台库中的 hashCode 实现相当。它很简单,速度也相当快,并且合理地将不相等的电话号码分散到不同的 hash 桶中。 +因为这个方法返回一个简单的确定性计算的结果,它的唯一输入是 PhoneNumber 实例中的三个重要字段,所以很明显,相等的 PhoneNumber 实例具有相等的 hash 码。实际上,这个方法是 PhoneNumber 的一个非常好的 hashCode 实现,与 Java 库中的 hashCode 实现相当。它很简单,速度也相当快,并且合理地将不相等的电话号码分散到不同的 hash 桶中。 While the recipe in this item yields reasonably good hash functions, they are not state-of-the-art. They are comparable in quality to the hash functions found in the Java platform libraries’ value types and are adequate for most uses. If you have a bona fide need for hash functions less likely to produce collisions, see Guava’s com.google.common.hash.Hashing [Guava]. -虽然本条目中的方法产生了相当不错的 hash 函数,但它们并不是最先进的。它们的质量可与 Java 平台库的值类型中的 hash 函数相媲美,对于大多数用途来说都是足够的。如果你确实需要不太可能产生冲突的 hash 函数,请参阅 Guava 的 com.google.common.hash.Hashing [Guava]。 +虽然本条目中的方法产生了相当不错的 hash 函数,但它们并不是最先进的。它们的质量可与 Java 库的值类型中的 hash 函数相媲美,对于大多数用途来说都是足够的。如果你确实需要不太可能产生冲突的 hash 函数,请参阅 Guava 的 com.google.common.hash.Hashing [Guava]。 The Objects class has a static method that takes an arbitrary number of objects and returns a hash code for them. This method, named hash, lets you write one-line hashCode methods whose quality is comparable to those written according to the recipe in this item. Unfortunately, they run more slowly because they entail array creation to pass a variable number of arguments, as well as boxing and unboxing if any of the arguments are of primitive type. This style of hash function is recommended for use only in situations where performance is not critical. Here is a hash function for PhoneNumber written using this technique: diff --git a/Chapter-3/Chapter-3-Item-12-Always-override-toString.md b/Chapter-3/Chapter-3-Item-12-Always-override-toString.md index 0ce0188..b9e6324 100644 --- a/Chapter-3/Chapter-3-Item-12-Always-override-toString.md +++ b/Chapter-3/Chapter-3-Item-12-Always-override-toString.md @@ -24,7 +24,7 @@ Programmers will generate diagnostic messages in this fashion whether or not you **When practical, the toString method should return all of the interesting information contained in the object,** as shown in the phone number example. It is impractical if the object is large or if it contains state that is not conducive to string representation. Under these circumstances,toString should return a summary such as Manhattan residential phone directory (1487536 listings) or Thread[main,5,main]. Ideally, the string should be self-explanatory. (The Thread example flunks this test.) A particularly annoying penalty for failing to include all of an object’s interesting information in its string representation is test failure reports that look like this: -**当实际使用时,toString 方法应该返回对象中包含的所有有趣信息,** 如电话号码示例所示。如果对象很大,或者包含不利于字符串表示的状态,那么这种方法是不切实际的。在这种情况下,toString 应该返回一个摘要,例如曼哈顿住宅电话目录(1487536 号清单)或 Thread[main,5,main]。理想情况下,字符串应该是不言自明的。(线程示例未能通过此测试。)如果没有在字符串表示中包含所有对象的有趣信息,那么一个特别恼人的惩罚就是测试失败报告,如下所示: +**当实际使用时,toString 方法应该返回对象中包含的所有有趣信息,** 如电话号码示例所示。如果对象很大,或者包含不利于字符串表示的状态,那么这种方法是不切实际的。在这种情况下,toString 应该返回一个摘要,例如曼哈顿住宅电话目录(1487536 号清单)或 Thread[main,5,main]。理想情况下,字符串应该是不言自明的。(线程示例未能通过此测试。)如果没有在字符串表示中包含所有对象的有趣信息,那么一个特别恼人的惩罚就是测试失败报告,如下所示: ``` Assertion failure: expected {abc, 123}, but was {abc, 123}. @@ -32,7 +32,7 @@ Assertion failure: expected {abc, 123}, but was {abc, 123}. One important decision you’ll have to make when implementing a toString method is whether to specify the format of the return value in the documentation. It is recommended that you do this for value classes, such as phone number or matrix. The advantage of specifying the format is that it serves as a standard, unambiguous, human-readable representation of the object. This representation can be used for input and output and in persistent human-readable data objects, such as CSV files. If you specify the format, it’s usually a good idea to provide a matching static factory or constructor so programmers can easily translate back and forth between the object and its string representation. This approach is taken by many value classes in the Java platform libraries, including BigInteger, BigDecimal, and most of the boxed primitive classes. -在实现 toString 方法时,你必须做的一个重要决定是是否在文档中指定返回值的格式。建议你针对值类(如电话号码或矩阵)这样做。指定格式的优点是,它可以作为对象的标准的、明确的、人类可读的表示。这种表示可以用于输入和输出,也可以用于持久的人类可读数据对象,比如 CSV 文件。如果指定了格式,提供一个匹配的静态工厂或构造函数通常是一个好主意,这样程序员就可以轻松地在对象及其字符串表示之间来回转换。Java 平台库中的许多值类都采用这种方法,包括 BigInteger、BigDecimal 和大多数包装类。 +在实现 toString 方法时,你必须做的一个重要决定是是否在文档中指定返回值的格式。建议你针对值类(如电话号码或矩阵)这样做。指定格式的优点是,它可以作为对象的标准的、明确的、人类可读的表示。这种表示可以用于输入和输出,也可以用于持久的人类可读数据对象,比如 CSV 文件。如果指定了格式,提供一个匹配的静态工厂或构造函数通常是一个好主意,这样程序员就可以轻松地在对象及其字符串表示之间来回转换。Java 库中的许多值类都采用这种方法,包括 BigInteger、BigDecimal 和大多数包装类。 The disadvantage of specifying the format of the toString return value is that once you’ve specified it, you’re stuck with it for life, assuming your class is widely used. Programmers will write code to parse the representation, to generate it, and to embed it into persistent data. If you change the representation in a future release, you’ll break their code and data, and they will yowl. By choosing not to specify a format, you preserve the flexibility to add information or improve the format in a subsequent release. diff --git a/Chapter-3/Chapter-3-Item-13-Override-clone-judiciously.md b/Chapter-3/Chapter-3-Item-13-Override-clone-judiciously.md index deb1e85..c0d800d 100644 --- a/Chapter-3/Chapter-3-Item-13-Override-clone-judiciously.md +++ b/Chapter-3/Chapter-3-Item-13-Override-clone-judiciously.md @@ -66,7 +66,7 @@ This mechanism is vaguely similar to constructor chaining, except that it isn’ Suppose you want to implement Cloneable in a class whose superclass provides a well-behaved clone method. First call super.clone. The object you get back will be a fully functional replica of the original. Any fields declared in your class will have values identical to those of the original. If every field contains a primitive value or a reference to an immutable object, the returned object may be exactly what you need, in which case no further processing is necessary. This is the case, for example, for the PhoneNumber class in Item 11, but note that **immutable classes should never provide a clone method** because it would merely encourage wasteful copying. With that caveat, here’s how a clone method for PhoneNumber would look: -假设你希望在一个类中实现 Cloneable,该类的超类提供了一个表现良好的克隆方法。第一个叫 super.clone。返回的对象将是原始对象的完整功能副本。类中声明的任何字段都具有与原始字段相同的值。如果每个字段都包含一个基元值或对不可变对象的引用,那么返回的对象可能正是你所需要的,在这种情况下不需要进一步的处理。例如,对于[Item-11](https://github.com/clxering/Effective-Java-3rd-edition-Chinese-English-bilingual/blob/master/Chapter-3/Chapter-3-Item-11-Always-override-hashCode-when-you-override-equals.md)中的 PhoneNumber 类就是这样,但是要注意,**不可变类永远不应该提供克隆方法**,因为它只会鼓励浪费复制。有了这个警告,以下是 PhoneNumber 的克隆方法的外观: +假设你希望在一个类中实现 Cloneable,该类的超类提供了一个表现良好的克隆方法。第一个叫 super.clone。返回的对象将是原始对象的完整功能副本。类中声明的任何字段都具有与原始字段相同的值。如果每个字段都包含一个基元值或对不可变对象的引用,那么返回的对象可能正是你所需要的,在这种情况下不需要进一步的处理。例如,对于[Item-11](https://github.com/clxering/Effective-Java-3rd-edition-Chinese-English-bilingual/blob/master/Chapter-3/Chapter-3-Item-11-Always-override-hashCode-when-you-override-equals.md)中的 PhoneNumber 类就是这样,但是要注意,**不可变类永远不应该提供克隆方法**,因为它只会鼓励浪费复制。有了这个警告,以下是 PhoneNumber 的克隆方法的外观: ``` // Clone method for class with no references to mutable state @@ -81,7 +81,7 @@ Suppose you want to implement Cloneable in a class whose superclass provides a w In order for this method to work, the class declaration for PhoneNumber would have to be modified to indicate that it implements Cloneable. Though Object’s clone method returns Object, this clone method returns PhoneNumber. It is legal and desirable to do this because Java supports covariant return types. In other words, an overriding method’s return type can be a subclass of the overridden method’s return type. This eliminates the need for casting in the client. We must cast the result of super.clone from Object to PhoneNumber before returning it, but the cast is guaranteed to succeed. -为了让这个方法工作,必须修改 PhoneNumber 的类声明,以表明它实现了 Cloneable。虽然 Object 的 clone 方法返回 Object,但是这个 clone 方法返回 PhoneNumber。这样做是合法的,也是可取的,因为 Java 支持协变返回类型。换句话说,覆盖方法的返回类型可以是被覆盖方法的返回类型的子类。这样就不需要在客户机中进行强制转换。我们必须打出超级的成绩。在返回对象之前从对象克隆到 PhoneNumber,但强制转换肯定会成功。 +为了让这个方法工作,必须修改 PhoneNumber 的类声明,以表明它实现了 Cloneable。虽然 Object 的 clone 方法返回 Object,但是这个 clone 方法返回 PhoneNumber。这样做是合法的,也是可取的,因为 Java 支持协变返回类型。换句话说,覆盖方法的返回类型可以是被覆盖方法的返回类型的子类。这样就不需要在客户端中进行强制转换。我们必须打出超级的成绩。在返回对象之前从对象克隆到 PhoneNumber,但强制转换肯定会成功。 The call to super.clone is contained in a try-catch block. This is because Object declares its clone method to throw CloneNotSupportedException, which is a checked exception. Because PhoneNumber implements Cloneable, we know the call to super.clone will succeed. The need for this boilerplate indicates that CloneNotSupportedException should have been unchecked (Item 71). @@ -150,7 +150,7 @@ Note that we do not have to cast the result of elements.clone to Object[]. Calli Note also that the earlier solution would not work if the elements field were final because clone would be prohibited from assigning a new value to the field. This is a fundamental problem: like serialization, the Cloneable architecture is incompatible with normal use of final fields referring to mutable objects, except in cases where the mutable objects may be safely shared between an object and its clone. In order to make a class cloneable, it may be necessary to remove final modifiers from some fields. -还要注意,如果元素字段是 final 的,早期的解决方案就无法工作,因为克隆将被禁止为字段分配新值。这是一个基本问题:与序列化一样,可克隆体系结构与正常使用引用可变对象的 final 字段不兼容,除非在对象与其克隆对象之间可以安全地共享可变对象。为了使类可克隆,可能需要从某些字段中删除最终修饰符。 +还要注意,如果元素字段是 final 的,早期的解决方案就无法工作,因为克隆将被禁止为字段分配新值。这是一个基本问题:与序列化一样,可克隆体系结构与正常使用引用可变对象的 final 字段不兼容,除非在对象与其克隆对象之间可以安全地共享可变对象。为了使类可克隆,可能需要从某些字段中删除最终修饰符。 It is not always sufficient(adj. 足够的;充分的) merely to call clone recursively. For example,suppose you are writing a clone method for a hash table whose internals consist of an array of buckets, each of which references the first entry in a linked list of key-value pairs. For performance, the class implements its own lightweight singly linked list instead of using java.util.LinkedList internally: @@ -253,11 +253,11 @@ Entry deepCopy() { A final approach to cloning complex mutable objects is to call super.clone, set all of the fields in the resulting object to their initial state,and then call higher-level methods to regenerate the state of the original object.In the case of our HashTable example, the buckets field would be initialized to a new bucket array, and the put(key, value) method (not shown) would be invoked for each key-value mapping in the hash table being cloned. This approach typically yields a simple, reasonably elegant clone method that does not run as quickly as one that directly manipulates the innards of the clone. While this approach is clean, it is antithetical to the whole Cloneable architecture because it blindly overwrites the field-by-field object copy that forms the basis of the architecture. -克隆复杂可变对象的最后一种方法是调用 super.clone,将结果对象中的所有字段设置为初始状态,然后调用更高级别的方法重新生成原始对象的状态。在我们的 HashTable 示例中,bucket 字段将初始化为一个新的 bucket 数组,并且对于克隆的 hash 表中的每个键值映射将调用 put(key, value)方法(未显示)。这种方法通常产生一个简单、相当优雅的克隆方法,它的运行速度不如直接操作克隆的内部的方法快。虽然这种方法很简洁,但它与整个可克隆体系结构是对立的,因为它盲目地覆盖了构成体系结构基础的逐字段对象副本。 +克隆复杂可变对象的最后一种方法是调用 super.clone,将结果对象中的所有字段设置为初始状态,然后调用更高级别的方法重新生成原始对象的状态。在我们的 HashTable 示例中,bucket 字段将初始化为一个新的 bucket 数组,并且对于克隆的 hash 表中的每个键值映射将调用 put(key, value)方法(未显示)。这种方法通常产生一个简单、相当优雅的克隆方法,它的运行速度不如直接操作克隆的内部的方法快。虽然这种方法很简洁,但它与整个可克隆体系结构是对立的,因为它盲目地覆盖了构成体系结构基础的逐字段对象副本。 Like a constructor, a clone method must never invoke an overridable method on the clone under construction (Item 19). If clone invokes a method that is overridden in a subclass, this method will execute before the subclass has had a chance to fix its state in the clone, quite possibly leading to corruption in the clone and the original. Therefore, the put(key, value) method discussed in the previous paragraph should be either final or private. (If it is private, it is presumably the “helper method” for a nonfinal public method.) -与构造函数一样,克隆方法决不能在正在构建的克隆上调用可覆盖方法(项目 19)。如果 clone 调用一个在子类中被重写的方法,这个方法将在子类有机会修复其在克隆中的状态之前执行,很可能导致克隆和原始的破坏。因此,前一段中讨论的 put(key, value)方法应该是 final 或 private 方法。(如果它是私有的,那么它可能是非最终公共方法的「助手方法」。) +与构造函数一样,克隆方法决不能在正在构建的克隆上调用可覆盖方法([Item-19](https://github.com/clxering/Effective-Java-3rd-edition-Chinese-English-bilingual/blob/master/Chapter-4/Chapter-4-Item-19-Design-and-document-for-inheritance-or-else-prohibit-it.md))。如果 clone 调用一个在子类中被重写的方法,这个方法将在子类有机会修复其在克隆中的状态之前执行,很可能导致克隆和原始的破坏。因此,前一段中讨论的 put(key, value)方法应该是 final 或 private 方法。(如果它是私有的,那么它可能是非最终公共方法的「助手方法」。) Object’s clone method is declared to throw CloneNotSupportedException, but overriding methods need not. **Public clone methods should omit the throws clause,** as methods that don’t throw checked exceptions are easier to use (Item 71). @@ -307,7 +307,7 @@ The copy constructor approach and its static factory variant have many advantage Furthermore, a copy constructor or factory can take an argument whose type is an interface implemented by the class. For example, by convention all generalpurpose collection implementations provide a constructor whose argument is of type Collection or Map. Interface-based copy constructors and factories,more properly known as conversion constructors and conversion factories, allow the client to choose the implementation type of the copy rather than forcing the client to accept the implementation type of the original. For example, suppose you have a HashSet, s, and you want to copy it as a TreeSet. The clone method can’t offer this functionality, but it’s easy with a conversion constructor:new TreeSet<>(s). -此外,复制构造函数或工厂可以接受类型为类实现的接口的参数。例如,按照约定,所有通用集合实现都提供一个构造函数,其参数为 collection 或 Map 类型。基于接口的复制构造函数和工厂(更确切地称为转换构造函数和转换工厂)允许客户端选择副本的实现类型,而不是强迫客户端接受原始的实现类型。例如,假设你有一个 HashSet s,并且希望将它复制为 TreeSet。克隆方法不能提供这种功能,但是使用转换构造函数很容易:new TreeSet<>(s)。 +此外,复制构造函数或工厂可以接受类型为类实现的接口的参数。例如,按照约定,所有通用集合实现都提供一个构造函数,其参数为 collection 或 Map 类型。基于接口的复制构造函数和工厂(更确切地称为转换构造函数和转换工厂)允许客户端选择副本的实现类型,而不是强迫客户端接受原始的实现类型。例如,假设你有一个 HashSet s,并且希望将它复制为 TreeSet。克隆方法不能提供这种功能,但是使用转换构造函数很容易:new TreeSet<>(s)。 Given all the problems associated(adj. 关联的;联合的) with Cloneable, new interfaces should not extend it, and new extendable classes should not implement it. While it’s less harmful for final classes to implement Cloneable, this should be viewed as a performance optimization, reserved for the rare cases where it is justified (Item 67). As a rule, copy functionality is best provided by constructors or factories. A notable exception to this rule is arrays, which are best copied with the clone method. diff --git a/Chapter-4/Chapter-4-Item-15-Minimize-the-accessibility-of-classes-and-members.md b/Chapter-4/Chapter-4-Item-15-Minimize-the-accessibility-of-classes-and-members.md index 916f441..b96b5bf 100644 --- a/Chapter-4/Chapter-4-Item-15-Minimize-the-accessibility-of-classes-and-members.md +++ b/Chapter-4/Chapter-4-Item-15-Minimize-the-accessibility-of-classes-and-members.md @@ -4,7 +4,7 @@ The single most important factor that distinguishes(vt/vi. 区别,区分;辨别) a well-designed component from a poorly designed one is the degree to which the component hides its internal data and other implementation details from other components. A welldesigned component hides all its implementation details, cleanly separating its API from its implementation. Components then communicate only through their APIs and are oblivious to each others’ inner workings. This concept, known as information hiding or encapsulation, is a fundamental tenet of software design [Parnas72]. -将设计良好的组件与设计糟糕的组件区别开来的最重要的因素是组件对其他组件隐藏内部数据和其他实现细节的程度。设计良好的组件隐藏了所有实现细节,干净地将 API 与实现分离。然后组件只通过它们的 api 进行通信,而不知道彼此的内部工作方式。这个概念被称为信息隐藏或封装,是软件设计的基本原则[Parnas72]。 +将设计良好的组件与设计糟糕的组件区别开来的最重要的因素是组件对其他组件隐藏内部数据和其他实现细节的程度。设计良好的组件隐藏了所有实现细节,干净地将 API 与实现分离。然后组件只通过它们的 api 进行通信,而不知道彼此的内部工作方式。这个概念被称为信息隐藏或封装,是软件设计的基本原则 [Parnas72]。 Information hiding is important for many reasons, most of which stem from the fact that it decouples(解耦) the components that comprise a system, allowing them to be developed, tested, optimized, used, understood, and modified in isolation(n. 隔离;孤立).This speeds up system development because components can be developed in parallel(n. 平行线;对比 adj. 平行的;类似的,相同的 vt. 使…与…平行). It eases(简化了) the burden(n. 负担;责任;vt. 使负担;烦扰) of maintenance(n. 维护,维修;保持) because components can be understood more quickly and debugged or replaced with little fear of harming other components. While information hiding does not, in and of itself, cause good performance, it enables effective performance tuning: once a system is complete and profiling(剖析) has determined which components are causing performance problems (Item 67), those components can be optimized without affecting the correctness of others. Information hiding increases software reuse because components that aren’t tightly coupled often prove useful in other contexts besides the ones for which they were developed. Finally, information hiding decreases the risk in building large systems because individual components may prove successful even if the system does not. @@ -12,7 +12,7 @@ Information hiding is important for many reasons, most of which stem from the fa Java has many facilities to aid in information hiding. The access control mechanism [JLS, 6.6] specifies the accessibility of classes, interfaces, and members. The accessibility of an entity is determined by the location of its declaration and by which, if any, of the access modifiers (private,protected, and public) is present on the declaration. Proper use of these modifiers is essential to information hiding. -Java 有许多工具来帮助隐藏信息。访问控制机制[JLS, 6.6]指定了类、接口和成员的可访问性。实体的可访问性由其声明的位置决定,如果有的话,则由声明中显示的访问修饰符(私有、受保护和公共)决定。正确使用这些修饰词是信息隐藏的关键。 +Java 有许多工具来帮助隐藏信息。访问控制机制 [JLS, 6.6] 指定了类、接口和成员的可访问性。实体的可访问性由其声明的位置决定,如果有的话,则由声明中显示的访问修饰符(私有、受保护和公共)决定。正确使用这些修饰词是信息隐藏的关键。 The rule of thumb is simple: make each class or member as inaccessible as possible. In other words, use the lowest possible access level consistent with the proper functioning of the software that you are writing. @@ -24,11 +24,11 @@ For top-level (non-nested) classes and interfaces, there are only two possible a If a package-private top-level class or interface is used by only one class,consider making the top-level class a private static nested class of the sole class that uses it (Item 24). This reduces its accessibility from all the classes in its package to the one class that uses it. But it is far more important to reduce the accessibility of a gratuitously public class than of a package-private top-level class: the public class is part of the package’s API, while the package-private top-level class is already part of its implementation. -如果包私有顶级类或接口只被一个类使用,那么考虑将顶级类设置为使用它的唯一类的私有静态嵌套类([Item-24](https://github.com/clxering/Effective-Java-3rd-edition-Chinese-English-bilingual/blob/master/Chapter-4/Chapter-4-Item-24-Favor-static-member-classes-over-nonstatic.md))。这降低了它从包中的所有类到使用它的类的可访问性。但是,减少免费公共类的可访问性比减少包-私有顶级类的可访问性重要得多:公共类是包的 API 的一部分,而包-私有顶级类已经是包的实现的一部分。 +如果包私有顶级类或接口只被一个类使用,那么考虑将顶级类设置为使用它的唯一类的私有静态嵌套类([Item-24](https://github.com/clxering/Effective-Java-3rd-edition-Chinese-English-bilingual/blob/master/Chapter-4/Chapter-4-Item-24-Favor-static-member-classes-over-nonstatic.md))。这降低了它从包中的所有类到使用它的类的可访问性。但是,减少免费公共类的可访问性比减少包-私有顶级类的可访问性重要得多:公共类是包的 API 的一部分,而包-私有顶级类已经是包的实现的一部分。 For members (fields, methods, nested classes, and nested interfaces), there are four possible access levels, listed here in order of increasing accessibility: -对于成员(字段、方法、嵌套类和嵌套接口),有四个可能的访问级别,这里列出了增加可访问性的顺序: +对于成员(字段、方法、嵌套类和嵌套接口),有四个可能的访问级别,这里列出了增加可访问性的顺序: - **private** —The member is accessible only from the top-level class where it is declared. @@ -36,11 +36,11 @@ For members (fields, methods, nested classes, and nested interfaces), there are - **package-private** —The member is accessible from any class in the package where it is declared. Technically known as default access, this is the access level you get if no access modifier is specified (except for interface members,which are public by default). -包级私有,成员可以从包中声明它的任何类访问。技术上称为默认访问,如果没有指定访问修饰符(接口成员除外,默认情况下,接口成员是公共的),就会得到这个访问级别。 +包级私有,成员可以从包中声明它的任何类访问。技术上称为默认访问,如果没有指定访问修饰符(接口成员除外,默认情况下,接口成员是公共的),就会得到这个访问级别。 - **protected** —The member is accessible from subclasses of the class where it is declared (subject to a few restrictions [JLS, 6.6.2]) and from any class in the package where it is declared. -保护,成员可以从声明它的类的子类(受一些限制[JLS, 6.6.2])和包中声明它的任何类访问。 +保护,成员可以从声明它的类的子类(受一些限制 [JLS, 6.6.2])和包中声明它的任何类访问。 - **public** —The member is accessible from anywhere. @@ -48,7 +48,7 @@ For members (fields, methods, nested classes, and nested interfaces), there are After carefully designing your class’s public API, your reflex(n. 反射;反映;映像;回复;习惯性思维 adj. 反射的;反省的;反作用的) should be to make all other members private. Only if another class in the same package really needs to access a member should you remove the private modifier, making the member package-private. If you find yourself doing this often, you should reexamine(重新审视) the design of your system to see if another decomposition(n. 分解,腐烂;变质) might yield classes that are better decoupled from one another. That said, both private and package-private members are part of a class’s implementation and do not normally impact its exported API. These fields can, however, “leak” into the exported API if the class implements Serializable (Items 86 and 87). -在仔细设计了类的公共 API 之后,你的反射应该是使所有其他成员都是私有的。只有当同一包中的另一个类确实需要访问一个成员时,你才应该删除私有修饰符,使成员包成为私有的。如果你发现自己经常这样做,那么你应该重新检查系统的设计,看看另一个分解是否会产生更好地相互分离的类。也就是说,私有成员和包私有成员都是类实现的一部分,通常不会影响其导出的 API。但是,如果类实现了 Serializable([Item-86](https://github.com/clxering/Effective-Java-3rd-edition-Chinese-English-bilingual/blob/master/Chapter-12/Chapter-12-Item-86-Implement-Serializable-with-great-caution.md)和[Item-87](https://github.com/clxering/Effective-Java-3rd-edition-Chinese-English-bilingual/blob/master/Chapter-12/Chapter-12-Item-87-Consider-using-a-custom-serialized-form.md)),这些字段可能会「泄漏」到导出的 API 中。 +在仔细设计了类的公共 API 之后,你的反射应该是使所有其他成员都是私有的。只有当同一包中的另一个类确实需要访问一个成员时,你才应该删除私有修饰符,使成员包成为私有的。如果你发现自己经常这样做,那么你应该重新检查系统的设计,看看另一个分解是否会产生更好地相互分离的类。也就是说,私有成员和包私有成员都是类实现的一部分,通常不会影响其导出的 API。但是,如果类实现了 Serializable([Item-86](https://github.com/clxering/Effective-Java-3rd-edition-Chinese-English-bilingual/blob/master/Chapter-12/Chapter-12-Item-86-Implement-Serializable-with-great-caution.md) 和 [Item-87](https://github.com/clxering/Effective-Java-3rd-edition-Chinese-English-bilingual/blob/master/Chapter-12/Chapter-12-Item-87-Consider-using-a-custom-serialized-form.md)),这些字段可能会「泄漏」到导出的 API 中。 For members of public classes, a huge increase in accessibility occurs when the access level goes from package-private to protected. A protected member is part of the class’s exported API and must be supported forever. Also, a protected member of an exported class represents a public commitment to an implementation detail (Item 19). The need for protected members should be relatively rare. @@ -56,7 +56,7 @@ For members of public classes, a huge increase in accessibility occurs when the There is a key rule that restricts your ability to reduce the accessibility of methods. If a method overrides a superclass method, it cannot have a more restrictive access level in the subclass than in the superclass [JLS, 8.4.8.3]. This is necessary to ensure that an instance of the subclass is usable anywhere that an instance of the superclass is usable (the Liskov substitution principle, see Item15). If you violate this rule, the compiler will generate an error message when you try to compile the subclass. A special case of this rule is that if a class implements an interface, all of the class methods that are in the interface must be declared public in the class. -有一个关键规则限制了你减少方法可访问性的能力。如果一个方法覆盖了超类方法,那么它在子类中的访问级别就不能比在超类[JLS, 8.4.8.3]中更严格。这对于确保子类的实例在超类的实例可用的任何地方都可用是必要的(Liskov 替换原则,请参阅[Item-15](https://github.com/clxering/Effective-Java-3rd-edition-Chinese-English-bilingual/blob/master/Chapter-4/Chapter-4-Item-15-Minimize-the-accessibility-of-classes-and-members.md))。如果违反此规则,编译器将在尝试编译子类时生成错误消息。这个规则的一个特殊情况是,如果一个类实现了一个接口,那么该接口中的所有类方法都必须在类中声明为 public。 +有一个关键规则限制了你减少方法可访问性的能力。如果一个方法覆盖了超类方法,那么它在子类中的访问级别就不能比在超类 [JLS, 8.4.8.3] 中更严格。这对于确保子类的实例在超类的实例可用的任何地方都可用是必要的(Liskov 替换原则,请参阅 [Item-15](https://github.com/clxering/Effective-Java-3rd-edition-Chinese-English-bilingual/blob/master/Chapter-4/Chapter-4-Item-15-Minimize-the-accessibility-of-classes-and-members.md))。如果违反此规则,编译器将在尝试编译子类时生成错误消息。这个规则的一个特殊情况是,如果一个类实现了一个接口,那么该接口中的所有类方法都必须在类中声明为 public。 To facilitate(vt. 促进;帮助;使容易) testing your code, you may be tempted to make a class, interface,or member more accessible than otherwise necessary. This is fine up to a point.It is acceptable to make a private member of a public class package-private in order to test it, but it is not acceptable to raise the accessibility any higher. In other words, it is not acceptable to make a class, interface, or member a part of a pack-age’s exported API to facilitate testing. Luckily, it isn’t necessary either because tests can be made to run as part of the package being tested, thus gaining access to its package-private elements. @@ -85,8 +85,7 @@ Beware of the fact that some IDEs generate accessors that return references to p ``` private static final Thing[] PRIVATE_VALUES = { ... }; -public static final List VALUES = -Collections.unmodifiableList(Arrays.asList(PRIVATE_VALUES)); +public static final List VALUES = Collections.unmodifiableList(Arrays.asList(PRIVATE_VALUES)); ``` Alternatively, you can make the array private and add a public method that returns a copy of a private array: @@ -96,7 +95,7 @@ Alternatively, you can make the array private and add a public method that retur ``` private static final Thing[] PRIVATE_VALUES = { ... }; public static final Thing[] values() { -return PRIVATE_VALUES.clone(); + return PRIVATE_VALUES.clone(); } ``` @@ -106,16 +105,16 @@ To choose between these alternatives, think about what the client is likely to d As of Java 9, there are two additional, implicit access levels introduced as part of the module system. A module is a grouping of packages, like a package is a grouping of classes. A module may explicitly export some of its packages via export declarations in its module declaration (which is by convention contained in a source file named module-info.java). Public and protected members of unexported packages in a module are inaccessible outside the module; within the module, accessibility is unaffected by export declarations. Using the module system allows you to share classes among packages within a module without making them visible to the entire world. Public and protected members of public classes in unexported packages give rise to the two implicit access levels, which are intramodular analogues of the normal public and protected levels. The need for this kind of sharing is relatively rare and can often be eliminated by rearranging the classes within your packages. -对于 Java 9,作为模块系统的一部分,还引入了另外两个隐式访问级别。模块是包的分组,就像包是类的分组一样。模块可以通过模块声明中的导出声明显式地导出它的一些包(按照约定包含在名为 module-info.java 的源文件中)。模块中未导出包的公共成员和受保护成员在模块外不可访问;在模块中,可访问性不受导出声明的影响。通过使用模块系统,你可以在模块内的包之间共享类,而不会让整个世界看到它们。未导出包中的公共类和受保护的成员产生了两个隐式访问级别,它们是正常公共级别和受保护级别的内部类似物。这种共享的需求相对较少,通常可以通过重新安排包中的类来消除。 +对于 Java 9,作为模块系统的一部分,还引入了另外两个隐式访问级别。模块是包的分组,就像包是类的分组一样。模块可以通过模块声明中的导出声明显式地导出它的一些包(按照约定包含在名为 `module-info.java` 的源文件中)。模块中未导出包的公共成员和受保护成员在模块外不可访问;在模块中,可访问性不受导出声明的影响。通过使用模块系统,你可以在模块内的包之间共享类,而不会让整个世界看到它们。未导出包中的公共类和受保护的成员产生了两个隐式访问级别,它们是正常公共级别和受保护级别的内部类似物。这种共享的需求相对较少,通常可以通过重新安排包中的类来消除。 Unlike the four main access levels, the two module-based levels are largely advisory. If you place a module’s JAR file on your application’s class path instead of its module path, the packages in the module revert to their nonmodular behavior: all of the public and protected members of the packages’ public classes have their normal accessibility, regardless of whether the packages are exported by the module [Reinhold, 1.2]. The one place where the newly introduced access levels are strictly enforced is the JDK itself: the unexported packages in the Java libraries are truly inaccessible outside of their modules. -与四个主要的访问级别不同,这两个基于模块的级别在很大程度上是顾问级别。如果你把一个模块的 JAR 文件在你的应用程序的类路径中,而不是模块路径,包模块恢复到他们 nonmodular 行为:所有的公共成员和保护成员包的公共类正常的可访问性,不管模块导出的包(Reinhold,1.2)。严格执行新引入的访问级别的一个地方是 JDK 本身:Java 库中未导出的包在其模块之外确实不可访问。 +与四个主要的访问级别不同,这两个基于模块的级别在很大程度上是顾问级别。如果你把一个模块的 JAR 文件在你的应用程序的类路径中,而不是模块路径,包模块恢复到他们 nonmodular 行为:所有的公共成员和保护成员包的公共类正常的可访问性,不管模块导出的包 [Reinhold,1.2]。严格执行新引入的访问级别的一个地方是 JDK 本身:Java 库中未导出的包在其模块之外确实不可访问。 Not only is the access protection afforded by modules of limited utility to the typical Java programmer, and largely advisory in nature; in order to take advantage of it, you must group your packages into modules, make all of their dependencies explicit in module declarations, rearrange your source tree, and take special actions to accommodate any access to non-modularized packages from within your modules [Reinhold, 3]. It is too early to say whether modules will achieve widespread use outside of the JDK itself. In the meantime, it seems best to avoid them unless you have a compelling need. -对于典型的 Java 程序员来说,访问保护不仅是有限实用的模块所提供的,而且本质上是建议性的;为了利用它,你必须将包分组到模块中,在模块声明中显式地声明它们的所有依赖项,重新安排源代码树,并采取特殊操作以适应从模块中对非模块化包的任何访问[Reinhold, 3]。现在说模块能否在 JDK 之外得到广泛使用还为时过早。与此同时,除非你有迫切的需求,否则最好还是避开它们。 +对于典型的 Java 程序员来说,访问保护不仅是有限实用的模块所提供的,而且本质上是建议性的;为了利用它,你必须将包分组到模块中,在模块声明中显式地声明它们的所有依赖项,重新安排源代码树,并采取特殊操作以适应从模块中对非模块化包的任何访问 [Reinhold, 3]。现在说模块能否在 JDK 之外得到广泛使用还为时过早。与此同时,除非你有迫切的需求,否则最好还是避开它们。 To summarize, you should reduce accessibility of program elements as much as possible (within reason). After carefully designing a minimal public API, you should prevent any stray classes, interfaces, or members from becoming part of the API. With the exception of public static final fields, which serve as constants,public classes should have no public fields. Ensure that objects referenced by public static final fields are immutable. -总之,你应该尽可能减少程序元素的可访问性(在合理的范围内)。在仔细设计了一个最小的公共 API 之后,你应该防止任何游离的类、接口或成员成为 API 的一部分。除了作为常量的公共静态 final 字段外,公共类应该没有公共字段。确保公共静态 final 字段引用的对象是不可变的。 +总之,你应该尽可能减少程序元素的可访问性(在合理的范围内)。在仔细设计了一个最小的公共 API 之后,你应该防止任何游离的类、接口或成员成为 API 的一部分。除了作为常量的公共静态 final 字段外,公共类应该没有公共字段。确保公共静态 final 字段引用的对象是不可变的。 diff --git a/Chapter-4/Chapter-4-Item-16-In-public-classes-use-accessor-methods-not-public-fields.md b/Chapter-4/Chapter-4-Item-16-In-public-classes-use-accessor-methods-not-public-fields.md index 66d7c69..6cab94d 100644 --- a/Chapter-4/Chapter-4-Item-16-In-public-classes-use-accessor-methods-not-public-fields.md +++ b/Chapter-4/Chapter-4-Item-16-In-public-classes-use-accessor-methods-not-public-fields.md @@ -4,7 +4,7 @@ Occasionally, you may be tempted to write degenerate classes that serve no purpose other than to group instance fields: -偶尔,你可能会忍不住编写一些只用于分组实例字段的退化类: +有时候,可能会编写一些退化类,这些类除了对实例字段进行分组之外,没有其他用途: ``` // Degenerate classes like this should not be public! @@ -16,7 +16,7 @@ class Point { Because the data fields of such classes are accessed directly, these classes do not offer the benefits of encapsulation(n. 封装;包装) (Item 15). You can’t change the representation(n. 代表;表现;表示法;陈述) without changing the API, you can’t enforce invariants, and you can’t take auxiliary(adj. 辅助的;副的;附加的) action when a field is accessed. Hard-line object-oriented programmers feel that such classes are anathema(n. 诅咒;革出教门;被诅咒者;令人厌恶的人) and should always be replaced by classes with private fields and public accessor methods (getters) and, for mutable classes, mutators (setters): -因为这些类的数据字段是直接访问的,所以这些类没有提供封装的好处([Item-15](https://github.com/clxering/Effective-Java-3rd-edition-Chinese-English-bilingual/blob/master/Chapter-4/Chapter-4-Item-15-Minimize-the-accessibility-of-classes-and-members.md))。不改变 API 就不能改变表示,不能强制执行不变量,也不能在访问字段时采取辅助操作。强硬的面向对象程序员认为这样的类是令人厌恶的,应该总是被私有字段和公共访问方法(getter)的类所取代,对于可变类,则是 mutators (setter): +因为这些类的数据字段是直接访问的,所以这些类没有提供封装的好处([Item-15](https://github.com/clxering/Effective-Java-3rd-edition-Chinese-English-bilingual/blob/master/Chapter-4/Chapter-4-Item-15-Minimize-the-accessibility-of-classes-and-members.md))。不改变 API 就不能改变表现形式,不能实施不变量,也不能在访问字段时采取辅助操作。坚持面向对象思维的程序员会认为这样的类是令人厌恶的,应该被使用私有字段和公共访问方法 getter 的类所取代,对于可变类,则是赋值方法 setter: ``` // Encapsulation of data by accessor methods and mutators @@ -36,19 +36,19 @@ class Point { Certainly, the hard-liners are correct when it comes to public classes: if a class is accessible outside its package, provide accessor methods to preserve the flexibility to change the class’s internal representation. If a public class exposes its data fields, all hope of changing its representation is lost because client code can be distributed far and wide. -当然,当涉及到公共类时,强硬派是正确的:如果类可以在包之外访问,那么提供访问器方法来保持更改类内部表示的灵活性。如果一个公共类公开其数据字段,那么改变其表示形式的所有希望都将落空,因为客户机代码可以广泛分发。 +当然,当涉及到公共类时,强硬派是正确的:如果类可以在包之外访问,那么提供访问器方法来保持更改类内部表示的灵活性。如果一个公共类公开其数据字段,那么改变其表示形式的所有希望都将落空,因为客户端代码可以广泛分发。 However, if a class is package-private or is a private nested class, there is nothing inherently wrong with exposing its data fields—assuming they do an adequate job of describing the abstraction provided by the class. This approach generates less visual clutter than the accessor-method approach, both in the class definition and in the client code that uses it. While the client code is tied to the class’s internal representation, this code is confined to the package containing the class. If a change in representation becomes desirable, you can make the change without touching any code outside the package. In the case of a private nested class, the scope of the change is further restricted to the enclosing class. -但是,如果一个类是包私有的或者是私有嵌套类,那么公开它的数据字段并没有什么本质上的错误——假设它们能够很好地描述类提供的抽象。无论是在类定义还是在使用它的客户机代码中,这种方法产生的视觉混乱都比访问方法少。虽然客户机代码与类的内部表示绑定在一起,但这段代码仅限于包含该类的包。如果想要对表示形式进行更改,你可以在不接触包外部任何代码的情况下进行更改。对于私有嵌套类,更改的范围进一步限制在封闭类中。 +但是,如果一个类是包私有的或者是私有嵌套类,那么公开它的数据字段并没有什么本质上的错误——假设它们能够很好地描述类提供的抽象。无论是在类定义还是在使用它的客户端代码中,这种方法产生的视觉混乱都比访问方法少。虽然客户端代码与类的内部表示绑定在一起,但这段代码仅限于包含该类的包。如果想要对表示形式进行更改,你可以在不接触包外部任何代码的情况下进行更改。对于私有嵌套类,更改的范围进一步限制在封闭类中。 Several classes in the Java platform libraries violate the advice that public classes should not expose fields directly. Prominent examples include the Point and Dimension classes in the java.awt package. Rather than examples to be emulated, these classes should be regarded as cautionary tales.As described in Item 67, the decision to expose the internals of the Dimension class resulted in a serious performance problem that is still with us today. -Java 平台库中的几个类违反了公共类不应该直接公开字段的建议。突出的例子包括 java.awt 包中的 Point 和维度类。这些课程不应被效仿,而应被视为警示故事。正如[Item-67](https://github.com/clxering/Effective-Java-3rd-edition-Chinese-English-bilingual/blob/master/Chapter-9/Chapter-9-Item-67-Optimize-judiciously.md)所述,决定公开维度类的内部结构导致了严重的性能问题,这种问题至今仍存在。 +Java 库中的几个类违反了公共类不应该直接公开字段的建议。突出的例子包括 `java.awt` 包中的 Point 和 Dimension。这些类不应被效仿,而应被视为警示。正如 [Item-67](https://github.com/clxering/Effective-Java-3rd-edition-Chinese-English-bilingual/blob/master/Chapter-9/Chapter-9-Item-67-Optimize-judiciously.md) 所述,公开 Dimension 类的内部结构导致了严重的性能问题,这种问题至今仍存在。 While it’s never a good idea for a public class to expose fields directly, it is less harmful if the fields are immutable. You can’t change the representation of such a class without changing its API, and you can’t take auxiliary actions when a field is read, but you can enforce invariants. For example, this class guarantees that each instance represents a valid time: -虽然公共类直接公开字段从来都不是一个好主意,但是如果字段是不可变的,那么危害就会小一些。你不能在不更改该类的 API 的情况下更改该类的表示形式,也不能在读取字段时采取辅助操作,但是你可以强制执行不变量。例如,这个类保证每个实例代表一个有效的时间: +虽然公共类直接公开字段从来都不是一个好主意,但是如果字段是不可变的,那么危害就会小一些。你不能在不更改该类的 API 的情况下更改该类的表现形式,也不能在读取字段时采取辅助操作,但是你可以实施不变量。例如,这个类保证每个实例代表一个有效的时间: ``` // Public class with exposed immutable fields - questionable @@ -57,6 +57,7 @@ public final class Time { private static final int MINUTES_PER_HOUR = 60; public final int hour; public final int minute; + public Time(int hour, int minute) { if (hour < 0 || hour >= HOURS_PER_DAY) throw new IllegalArgumentException("Hour: " + hour); @@ -70,4 +71,4 @@ public final class Time { In summary, public classes should never expose(vt. 揭露,揭发;使曝光;显示) mutable fields. It is less harmful, though still questionable(adj. 可疑的;有问题的), for public classes to expose immutable fields.It is, however, sometimes desirable for package-private or private nested classes to expose fields, whether mutable or immutable. -总之,公共类不应该公开可变字段。对于公共类来说,公开不可变字段的危害要小一些,尽管仍然值得怀疑。然而,有时候包私有或私有嵌套类需要公开字段,无论是可变的还是不可变的。 +总之,公共类不应该公开可变字段。对于公共类来说,公开不可变字段的危害要小一些,但仍然存在潜在的问题。然而,有时候包私有或私有嵌套类需要公开字段,无论这个类是可变的还是不可变的。 diff --git a/Chapter-4/Chapter-4-Item-17-Minimize-mutability.md b/Chapter-4/Chapter-4-Item-17-Minimize-mutability.md index 0619fe6..6407f2e 100644 --- a/Chapter-4/Chapter-4-Item-17-Minimize-mutability.md +++ b/Chapter-4/Chapter-4-Item-17-Minimize-mutability.md @@ -4,15 +4,15 @@ An immutable(adj. 不变的;不可变的;不能变的) class is simply a class whose instances cannot be modified(v. 修改;adj. 改进的,修改的). All of the information contained in each instance is fixed for the lifetime of the object,so no changes can ever be observed. The Java platform libraries contain many immutable classes, including String, the boxed primitive classes, and BigInteger and BigDecimal. There are many good reasons for this:Immutable classes are easier to design, implement, and use than mutable classes.They are less prone(adj. 俯卧的;有…倾向的,易于…的) to error and are more secure. -不可变类就是一个实例不能被修改的类。每个实例中包含的所有信息在对象的生命周期内都是固定的,因此永远不会观察到任何更改。Java 平台库包含许多不可变的类,包括 String、基本数据类型的包装类、BigInteger 和 BigDecimal。有很多很好的理由:不可变类比可变类更容易设计、实现和使用。它们不太容易出错,而且更安全。 +不可变类就是一个实例不能被修改的类。每个实例中包含的所有信息在对象的生命周期内都是固定的,因此永远不会观察到任何更改。Java 库包含许多不可变的类,包括 String、基本数据类型的包装类、BigInteger 和 BigDecimal。有很多很好的理由:不可变类比可变类更容易设计、实现和使用。它们不太容易出错,而且更安全。 To make a class immutable, follow these five rules: -要使类不可变,请遵循以下 5 条规则: +要使类不可变,请遵循以下 5 条规则: 1. **Don’t provide methods that modify the object’s state** (known as mutators). -**不要提供修改对象状态的方法**(被称为调整器) +**不要提供修改对象状态的方法**(被称为赋值方法) 2. **Ensure that the class can’t be extended.** This prevents careless or malicious(adj. 恶意的;恶毒的;蓄意的;怀恨的) subclasses from compromising(adj. 妥协的;让步的) the immutable behavior of the class by behaving as if the object’s state has changed. Preventing subclassing is generally accomplished by making the class final, but there is an alternative that we’ll discuss later. @@ -20,11 +20,11 @@ To make a class immutable, follow these five rules: 3. **Make all fields final.** This clearly expresses your intent in a manner that is enforced(v. 执行) by the system. Also, it is necessary to ensure correct behavior if a reference to a newly created instance is passed from one thread to another without synchronization, as spelled out in the memory model [JLS, 17.5;Goetz06, 16]. -**使所有字段为终态。** 这清楚地表达了你的意图,方式是由系统执行的。同样,如果引用新创建的实例在没有同步的情况下从一个线程传递到另一个线程,那么就有必要确保正确的行为,就像内存模型中描述的那样[JLS, 17.5;Goetz06, 16]。 +**所有字段用 final 修饰。** 这清楚地表达了你的意图,方式是由系统执行的。同样,如果引用新创建的实例在没有同步的情况下从一个线程传递到另一个线程,那么就有必要确保正确的行为,就像内存模型中描述的那样 [JLS, 17.5;Goetz06, 16]。 4. **Make all fields private.** This prevents clients from obtaining access to mutable objects referred to by fields and modifying these objects directly.While it is technically permissible for immutable classes to have public final fields containing primitive values or references to immutable objects, it is not recommended because it precludes changing the internal representation in a later release (Items 15 and 16). -**使所有字段为私有。** 这将阻止客户端访问字段引用的可变对象并直接修改这些对象。虽然在技术上允许不可变类拥有包含基元值或对不可变对象的引用的公共 final 字段,但不建议这样做,因为它排除了在以后的版本中更改内部表示([Item-15](https://github.com/clxering/Effective-Java-3rd-edition-Chinese-English-bilingual/blob/master/Chapter-4/Chapter-4-Item-15-Minimize-the-accessibility-of-classes-and-members.md)和[Item-16](https://github.com/clxering/Effective-Java-3rd-edition-Chinese-English-bilingual/blob/master/Chapter-4/Chapter-4-Item-16-In-public-classes-use-accessor-methods-not-public-fields.md))。 +**使所有字段为私有。** 这将阻止客户端访问字段引用的可变对象并直接修改这些对象。虽然在技术上允许不可变类拥有包含基元值或对不可变对象的引用的公共 final 字段,但不建议这样做,因为它排除了在以后的版本中更改内部表示([Item-15](https://github.com/clxering/Effective-Java-3rd-edition-Chinese-English-bilingual/blob/master/Chapter-4/Chapter-4-Item-15-Minimize-the-accessibility-of-classes-and-members.md) 和 [Item-16](https://github.com/clxering/Effective-Java-3rd-edition-Chinese-English-bilingual/blob/master/Chapter-4/Chapter-4-Item-16-In-public-classes-use-accessor-methods-not-public-fields.md))。 5. **Ensure exclusive access to any mutable components.** If your class has any fields that refer to mutable objects, ensure that clients of the class cannot obtain references to these objects. Never initialize such a field to a clientprovided object reference or return the field from an accessor. Make defensive copies (Item 50) in constructors, accessors, and readObject methods (Item 88). @@ -32,7 +32,7 @@ To make a class immutable, follow these five rules: Many of the example classes in previous items are immutable. One such class is PhoneNumber in Item 11, which has accessors for each attribute but no corresponding mutators. Here is a slightly more complex example: -前面项目中的许多示例类都是不可变的。其中一个类是[Item-11](https://github.com/clxering/Effective-Java-3rd-edition-Chinese-English-bilingual/blob/master/Chapter-3/Chapter-3-Item-11-Always-override-hashCode-when-you-override-equals.md)中的 PhoneNumber,它对每个属性都有访问器,但没有相应的 mutators。下面是一个稍微复杂一点的例子: +前面项目中的许多示例类都是不可变的。其中一个类是 [Item-11](https://github.com/clxering/Effective-Java-3rd-edition-Chinese-English-bilingual/blob/master/Chapter-3/Chapter-3-Item-11-Always-override-hashCode-when-you-override-equals.md) 中的 PhoneNumber,它对每个属性都有访问器,但没有相应的赋值方法。下面是一个稍微复杂一点的例子: ``` // Immutable complex number class @@ -84,7 +84,7 @@ public final class Complex { This class represents a complex number (a number with both real and imaginary parts). In addition to the standard Object methods, it provides accessors for the real and imaginary parts and provides the four basic arithmetic operations: addition, subtraction, multiplication, and division. Notice how the arithmetic operations create and return a new Complex instance rather than modifying this instance. This pattern is known as the functional approach because methods return the result of applying a function to their operand,without modifying it. Contrast it to the procedural or imperative approach in which methods apply a procedure to their operand, causing its state to change.Note that the method names are prepositions (such as plus) rather than verbs (such as add). This emphasizes the fact that methods don’t change the values of the objects. The BigInteger and BigDecimal classes did not obey this naming convention, and it led to many usage errors. -这个类表示一个复数(一个包含实部和虚部的数)。除了标准的对象方法之外,它还为实部和虚部提供访问器,并提供四种基本的算术运算:加法、减法、乘法和除法。需要注意的是,算术操作如何创建和返回一个新的复杂实例,而不是修改这个实例。这种模式称为泛函方法,因为方法返回对其操作数应用函数的结果,而不修改它。将其与方法将过程应用于其操作数的过程或命令式方法进行对比,使其状态发生变化。注意,方法名是介词(如 plus),而不是动词(如 add)。这强调了一个事实,方法不会改变对象的值。BigInteger 和 BigDecimal 类不遵守这个命名约定,导致了许多使用错误。 +这个类表示一个复数(一个包含实部和虚部的数)。除了标准的对象方法之外,它还为实部和虚部提供访问器,并提供四种基本的算术运算:加法、减法、乘法和除法。需要注意的是,算术操作如何创建和返回一个新的复杂实例,而不是修改这个实例。这种模式称为泛函方法,因为方法返回对其操作数应用函数的结果,而不修改它。将其与方法将过程应用于其操作数的过程或命令式方法进行对比,使其状态发生变化。注意,方法名是介词(如 plus),而不是动词(如 add)。这强调了一个事实,方法不会改变对象的值。BigInteger 和 BigDecimal 类不遵守这个命名约定,导致了许多使用错误。 The functional approach(n. 方法;途径;接近) may appear unnatural if you’re not familiar(adj. 熟悉的;常见的;亲近的) with it,but it enables immutability, which has many advantages. **Immutable objects are simple.** An immutable object can be in exactly one state, the state in which it was created. If you make sure that all constructors establish class invariants, then it is guaranteed that these invariants will remain true for all time, with no further effort on your part or on the part of the programmer who uses the class. Mutable objects, on the other hand, can have arbitrarily complex state spaces. If the documentation does not provide a precise description of the state transitions performed by mutator methods, it can be difficult or impossible to use a mutable class reliably. @@ -92,7 +92,7 @@ The functional approach(n. 方法;途径;接近) may appear unnatural if **Immutable objects are inherently thread-safe; they require no synchronization.** They cannot be corrupted by multiple threads accessing them concurrently. This is far and away the easiest approach to achieve thread safety.Since no thread can ever observe any effect of another thread on an immutable object, **immutable objects can be shared freely.** Immutable classes should therefore encourage clients to reuse existing instances wherever possible. One easy way to do this is to provide public static final constants for commonly used values. For example, the Complex class might provide these constants: -不可变对象本质上是线程安全的;它们不需要同步。它们不能被多线程并发地访问而损坏。这无疑是实现线程安全的最简单方法。由于任何线程都无法观察到另一个线程对不可变对象的任何影响,因此 **可以自由共享不可变对象。** 因此,不可变类应该鼓励客户尽可能重用现有的实例。一种简单的方法是为常用值提供公共静态最终常量。例如,复杂类可能提供以下常量: +不可变对象本质上是线程安全的;它们不需要同步。它们不能被多线程并发地访问而损坏。这无疑是实现线程安全的最简单方法。由于任何线程都无法观察到另一个线程对不可变对象的任何影响,因此 **可以自由共享不可变对象。** 因此,不可变类应该鼓励客户尽可能重用现有的实例。一种简单的方法是为常用值提供公共静态最终常量。例如,复杂类可能提供以下常量: ``` public static final Complex ZERO = new Complex(0, 0); @@ -102,7 +102,7 @@ public static final Complex I = new Complex(0, 1); This approach can be taken one step further. An immutable class can provide static factories (Item 1) that cache frequently requested instances to avoid creating new instances when existing ones would do. All the boxed primitive classes and BigInteger do this. Using such static factories causes clients to share instances instead of creating new ones, reducing memory footprint and garbage collection costs. Opting for static factories in place of public constructors when designing a new class gives you the flexibility to add caching later, without modifying clients. -这种方法可以更进一步。不可变类可以提供静态工厂([Item-1](https://github.com/clxering/Effective-Java-3rd-edition-Chinese-English-bilingual/blob/master/Chapter-2/Chapter-2-Item-1-Consider-static-factory-methods-instead-of-constructors.md)),这些工厂缓存经常请求的实例,以避免在现有实例可用时创建新实例。所有包装类和 BigInteger 都是这样做的。使用这种静态工厂会导致客户机共享实例而不是创建新实例,从而减少内存占用和垃圾收集成本。在设计新类时,选择静态工厂而不是公共构造函数,这使你能够灵活地在以后添加缓存,而无需修改客户机。 +这种方法可以更进一步。不可变类可以提供静态工厂([Item-1](https://github.com/clxering/Effective-Java-3rd-edition-Chinese-English-bilingual/blob/master/Chapter-2/Chapter-2-Item-1-Consider-static-factory-methods-instead-of-constructors.md)),这些工厂缓存经常请求的实例,以避免在现有实例可用时创建新实例。所有包装类和 BigInteger 都是这样做的。使用这种静态工厂会导致客户端共享实例而不是创建新实例,从而减少内存占用和垃圾收集成本。在设计新类时,选择静态工厂而不是公共构造函数,这使你能够灵活地在以后添加缓存,而无需修改客户端。 A consequence(n. 结果;重要性;推论) of the fact that immutable objects can be shared freely is that you never have to make defensive copies of them (Item 50). In fact, you never have to make any copies at all because the copies would be forever equivalent to the originals. Therefore, you need not and should not provide a clone method or copy constructor (Item 13) on an immutable class. This was not well understood in the early days of the Java platform, so the String class does have a copy constructor, but it should rarely, if ever, be used (Item 6). @@ -114,7 +114,7 @@ A consequence(n. 结果;重要性;推论) of the fact that immutable obj **Immutable objects make great building blocks for other objects,** whether mutable or immutable. It’s much easier to maintain the invariants of a complex object if you know that its component objects will not change underneath it. A special case of this principle is that immutable objects make great map keys and set elements: you don’t have to worry about their values changing once they’re in the map or set, which would destroy the map or set’s invariants. -**不可变对象是其他对象的很好的构建模块,** 无论是可变的还是不可变的。如果知道复杂对象的组件对象不会在其下面发生更改,那么维护复杂对象的不变性就会容易得多。这个原则的一个特殊情况是,不可变对象会生成很棒的 map 键和 set 元素:你不必担心它们的值在 map 或 set 中发生变化,这会破坏 map 或 set 的不变量。 +**不可变对象是其他对象的很好的构建模块,** 无论是可变的还是不可变的。如果知道复杂对象的组件对象不会在其下面发生更改,那么维护复杂对象的不变性就会容易得多。这个原则的一个特殊情况是,不可变对象会生成很棒的 map 键和 set 元素:你不必担心它们的值在 map 或 set 中发生变化,这会破坏 map 或 set 的不变量。 **Immutable objects provide failure atomicity for free** (Item 76). Their state never changes, so there is no possibility of a temporary inconsistency. @@ -122,7 +122,7 @@ A consequence(n. 结果;重要性;推论) of the fact that immutable obj **The major disadvantage of immutable classes is that they require a separate object for each distinct value.** Creating these objects can be costly,especially if they are large. For example, suppose that you have a million-bit BigInteger and you want to change its low-order bit: -不可变类的主要缺点是每个不同的值都需要一个单独的对象。创建这些对象的成本可能很高,尤其是如果对象很大的话。例如,假设你有一个百万比特的大整数,你想改变它的低阶位: +不可变类的主要缺点是每个不同的值都需要一个单独的对象。创建这些对象的成本可能很高,尤其是如果对象很大的话。例如,假设你有一个百万比特的大整数,你想改变它的低阶位: ``` BigInteger moby = ...; @@ -131,7 +131,7 @@ moby = moby.flipBit(0); The flipBit method creates a new BigInteger instance, also a million bits long, that differs from the original in only one bit. The operation requires time and space proportional to the size of the BigInteger. Contrast this to java.util.BitSet. Like BigInteger, BitSet represents an arbitrarily long sequence of bits, but unlike BigInteger, BitSet is mutable. The BitSet class provides a method that allows you to change the state of a single bit of a million-bit instance in constant time: -flipBit 方法创建了一个新的 BigInteger 实例,也有 100 万比特长,只在一个比特上与原始的不同。该操作需要与 BigInteger 的大小成比例的时间和空间。与 java.util.BitSet 形成对比。与 BigInteger 一样,BitSet 表示任意长的位序列,但与 BigInteger 不同,BitSet 是可变的。BitSet 类提供了一种方法,可以让你在固定的时间内改变百万比特实例的单个位的状态: +flipBit 方法创建了一个新的 BigInteger 实例,也有 100 万比特长,只在一个比特上与原始的不同。该操作需要与 BigInteger 的大小成比例的时间和空间。与 `java.util.BitSet` 形成对比。与 BigInteger 一样,BitSet 表示任意长的位序列,但与 BigInteger 不同,BitSet 是可变的。BitSet 类提供了一种方法,可以让你在固定的时间内改变百万比特实例的单个位的状态: ``` BitSet moby = ...; @@ -140,11 +140,11 @@ moby.flip(0); The performance problem is magnified if you perform a multistep operation that generates a new object at every step, eventually discarding all objects except the final result. There are two approaches to coping with this problem. The first is to guess which multistep operations will be commonly required and to provide them as primitives. If a multistep operation is provided as a primitive, the immutable class does not have to create a separate object at each step. Internally,the immutable class can be arbitrarily clever. For example, BigInteger has a package-private mutable “companion class” that it uses to speed up multistep operations such as modular exponentiation. It is much harder to use the mutable companion class than to use BigInteger, for all of the reasons outlined earlier. Luckily, you don’t have to use it: the implementors of BigInteger did the hard work for you. -如果执行多步操作,在每一步生成一个新对象,最终丢弃除最终结果之外的所有对象,那么性能问题就会增大。有两种方法可以解决这个问题。第一种方法是猜测通常需要哪些多步操作,并将它们作为原语提供。如果将多步操作作为基元提供,则不可变类不必在每个步骤中创建单独的对象。在内部,不可变类可以任意聪明。例如,BigInteger 有一个包私有的可变「同伴类」,它使用这个类来加速多步操作,比如模块化求幂。由于前面列出的所有原因,使用可变同伴类要比使用 BigInteger 难得多。幸运的是,你不必使用它:BigInteger 的实现者为你做了艰苦的工作。 +如果执行多步操作,在每一步生成一个新对象,最终丢弃除最终结果之外的所有对象,那么性能问题就会增大。有两种方法可以解决这个问题。第一种方法是猜测通常需要哪些多步操作,并将它们作为原语提供。如果将多步操作作为基元提供,则不可变类不必在每个步骤中创建单独的对象。在内部,不可变类可以任意聪明。例如,BigInteger 有一个包私有的可变「伴随类」,它使用这个类来加速多步操作,比如模块化求幂。由于前面列出的所有原因,使用可变伴随类要比使用 BigInteger 难得多。幸运的是,你不必使用它:BigInteger 的实现者为你做了艰苦的工作。 The package-private mutable companion class approach works fine if you can accurately predict which complex operations clients will want to perform on your immutable class. If not, then your best bet is to provide a public mutable companion class. The main example of this approach in the Java platform libraries is the String class, whose mutable companion is StringBuilder (and its obsolete predecessor, StringBuffer). -如果你能够准确地预测客户机希望在不可变类上执行哪些复杂操作,那么包私有可变伴侣类方法就可以很好地工作。如果不是,那么你最好的选择就是提供一个公共可变伴侣类。这种方法在 Java 平台库中的主要示例是 String 类,它的可变伙伴是 StringBuilder(及其过时的前身 StringBuffer)。 +如果你能够准确地预测客户端希望在不可变类上执行哪些复杂操作,那么包私有可变伴随类方法就可以很好地工作。如果不是,那么你最好的选择就是提供一个公共可变伴随类。这种方法在 Java 库中的主要示例是 String 类,它的可变伴随类是 StringBuilder(及其过时的前身 StringBuffer)。 Now that you know how to make an immutable class and you understand the pros and cons of immutability, let’s discuss a few design alternatives. Recall that to guarantee immutability, a class must not permit itself to be subclassed. This can be done by making the class final, but there is another, more flexible alternative. Instead of making an immutable class final, you can make all of its constructors private or package-private and add public static factories in place of the public constructors (Item 1). To make this concrete, here’s how Complex would look if you took this approach: @@ -187,7 +187,7 @@ The list of rules for immutable classes at the beginning of this item says that For example, PhoneNumber’s hashCode method (Item 11, page 53) computes the hash code the first time it’s invoked and caches it in case it’s invoked again. This technique, an example of lazy initialization (Item 83), is also used by String. -例如,PhoneNumber 的 hashCode 方法([Item-11](https://github.com/clxering/Effective-Java-3rd-edition-Chinese-English-bilingual/blob/master/Chapter-3/Chapter-3-Item-11-Always-override-hashCode-when-you-override-equals.md),第 53 页)在第一次调用时计算哈希代码,并缓存它,以防再次调用它。这个技术是一个延迟初始化的例子([Item-83](https://github.com/clxering/Effective-Java-3rd-edition-Chinese-English-bilingual/blob/master/Chapter-11/Chapter-11-Item-83-Use-lazy-initialization-judiciously.md)),String 也使用这个技术。 +例如,PhoneNumber 的 hashCode 方法([Item-11](https://github.com/clxering/Effective-Java-3rd-edition-Chinese-English-bilingual/blob/master/Chapter-3/Chapter-3-Item-11-Always-override-hashCode-when-you-override-equals.md),第 53 页)在第一次调用时计算哈希代码,并缓存它,以防再次调用它。这个技术是一个延迟初始化的例子([Item-83](https://github.com/clxering/Effective-Java-3rd-edition-Chinese-English-bilingual/blob/master/Chapter-11/Chapter-11-Item-83-Use-lazy-initialization-judiciously.md)),String 也使用这个技术。 One caveat should be added concerning serializability. If you choose to have your immutable class implement Serializable and it contains one or more fields that refer to mutable objects, you must provide an explicit readObject or readResolve method, or use the ObjectOutputStream.writeUnshared and ObjectInputStream.readUnshared methods, even if the default serialized form is acceptable. Otherwise an attacker could create a mutable instance of your class. This topic is covered in detail in Item 88. @@ -195,7 +195,7 @@ One caveat should be added concerning serializability. If you choose to have you To summarize, resist the urge to write a setter for every getter. **Classes should be immutable unless there’s a very good reason to make them mutable.** Immutable classes provide many advantages, and their only disadvantage is the potential for performance problems under certain circumstances. You should always make small value objects, such as PhoneNumber and Complex, immutable. (There are several classes in the Java platform libraries, such as java.util.Date and java.awt.Point, that should have been immutable but aren’t.) You should seriously consider making larger value objects, such as String and BigInteger, immutable as well. You should provide a public mutable companion class for your immutable class only once you’ve confirmed that it’s necessary to achieve satisfactory performance (Item 67). -总结一下,抵制为每个 getter 编写 setter 的冲动。类应该是不可变的,除非有很好的理由让它们可变。不可变类提供了许多优点,它们唯一的缺点是在某些情况下可能出现性能问题。你应该始终创建小的值对象,例如 PhoneNumber 和 Complex、stable。(Java 平台库中有几个类,比如 java.util.Date 和 java.awt.Point,这本来是不可改变的,但事实并非如此。你应该认真考虑将较大的值对象(如 String 和 BigInteger)设置为不可变的。只有在确认了实现满意性能的必要性之后,才应该为不可变类提供一个公共可变伴侣类([Item-67](https://github.com/clxering/Effective-Java-3rd-edition-Chinese-English-bilingual/blob/master/Chapter-9/Chapter-9-Item-67-Optimize-judiciously.md))。 +总结一下,抵制为每个 getter 编写 setter 的冲动。类应该是不可变的,除非有很好的理由让它们可变。不可变类提供了许多优点,它们唯一的缺点是在某些情况下可能出现性能问题。你应该始终创建小的值对象,例如 PhoneNumber 和 Complex、stable。(Java 库中有几个类,比如 `java.util.Date` 和 `java.awt.Point`,这本来是不可改变的,但事实并非如此。)你应该认真考虑将较大的值对象(如 String 和 BigInteger)设置为不可变的。只有在确认了实现满意性能的必要性之后,才应该为不可变类提供一个公共可变伴随类([Item-67](https://github.com/clxering/Effective-Java-3rd-edition-Chinese-English-bilingual/blob/master/Chapter-9/Chapter-9-Item-67-Optimize-judiciously.md))。 There are some classes for which immutability is impractical. **If a class cannot be made immutable, limit its mutability as much as possible.** Reducing the number of states in which an object can exist makes it easier to reason about the object and reduces the likelihood of errors. Therefore, make every field final unless there is a compelling reason to make it nonfinal. Combining the advice of this item with that of Item 15, your natural inclination should be to **declare every field private final unless there’s a good reason to do otherwise.** @@ -207,8 +207,8 @@ There are some classes for which immutability is impractical. **If a class canno The CountDownLatch class exemplifies these principles. It is mutable, but its state space is kept intentionally small. You create an instance, use it once, and it’s done: once the countdown latch’s count has reached zero, you may not reuse it. -CountDownLatch 类演示了这些原理。它是可变的,但它的状态空间故意保持小。你创建一个实例,使用一次,就完成了:一旦倒计时锁的计数达到零,你可能不会重用它。 +CountDownLatch 类演示了这些原理。它是可变的,但它的状态空间故意保持小。你创建一个实例,使用一次,就完成了:一旦倒计时锁的计数达到零,你可能不会重用它。 A final note should be added concerning the Complex class in this item. This example was meant only to illustrate immutability. It is not an industrial-strength complex number implementation. It uses the standard formulas for complex multiplication and division, which are not correctly rounded and provide poor semantics for complex NaNs and infinities [Kahan91, Smith62, Thomas94]. -关于这个项目中的复杂类,应该添加最后一个注意事项。这个例子只是为了说明不变性。它不是一个工业强度的复数实现。它使用了复杂乘法和除法的标准公式,这些公式没有被正确地四舍五入,并且为复杂的 NaNs 和 infinities 提供了糟糕的语义[Kahan91, Smith62, Thomas94]。 +关于这个项目中的复杂类,应该添加最后一个注意事项。这个例子只是为了说明不变性。它不是一个工业强度的复数实现。它使用了复杂乘法和除法的标准公式,这些公式没有被正确地四舍五入,并且为复杂的 NaNs 和 infinities 提供了糟糕的语义 [Kahan91, Smith62, Thomas94]。 diff --git a/Chapter-4/Chapter-4-Item-18-Favor-composition-over-inheritance.md b/Chapter-4/Chapter-4-Item-18-Favor-composition-over-inheritance.md index a62d145..d959772 100644 --- a/Chapter-4/Chapter-4-Item-18-Favor-composition-over-inheritance.md +++ b/Chapter-4/Chapter-4-Item-18-Favor-composition-over-inheritance.md @@ -4,7 +4,7 @@ Inheritance is a powerful way to achieve(vt. 取得;获得;实现;成功) code reuse(n. 重新使用,再用), but it is not always the best tool for the job. Used inappropriately(adv. 不适当地), it leads to fragile(adj. 脆的;易碎的) software. It is safe to use inheritance within a package, where the subclass and the superclass implementations are under the control of the same programmers. It is also safe to use inheritance when extending classes specifically designed and documented for extension (Item 19). Inheriting from ordinary concrete classes across package boundaries, however, is dangerous. As a reminder, this book uses the word “inheritance” to mean implementation inheritance (when one class extends another). The problems discussed in this item do not apply to interface inheritance (when a class implements an interface or when one interface extends another). -继承是实现代码重用的一种强大方法,但它并不总是最佳的工具。使用不当会导致软件脆弱。在包中使用继承是安全的,其中子类和超类实现由相同的程序员控制。在扩展专门为扩展设计和文档化的类时使用继承也是安全的([Item-19](https://github.com/clxering/Effective-Java-3rd-edition-Chinese-English-bilingual/blob/master/Chapter-4/Chapter-4-Item-19-Design-and-document-for-inheritance-or-else-prohibit-it.md))。然而,对普通的具体类进行跨包边界的继承是危险的。作为提醒,本书使用「继承」一词来表示实现继承(当一个类扩展另一个类时)。本项目中讨论的问题不适用于接口继承(当类实现接口或一个接口扩展另一个接口时)。 +继承是实现代码重用的一种强大方法,但它并不总是最佳的工具。使用不当会导致软件脆弱。在包中使用继承是安全的,其中子类和超类实现由相同的程序员控制。在扩展专门为扩展设计和文档化的类时使用继承也是安全的([Item-19](https://github.com/clxering/Effective-Java-3rd-edition-Chinese-English-bilingual/blob/master/Chapter-4/Chapter-4-Item-19-Design-and-document-for-inheritance-or-else-prohibit-it.md))。然而,对普通的具体类进行跨包边界的继承是危险的。作为提醒,本书使用「继承」一词来表示实现继承(当一个类扩展另一个类时)。本项目中讨论的问题不适用于接口继承(当类实现接口或一个接口扩展另一个接口时)。 Unlike method invocation, inheritance violates encapsulation [Snyder86].In other words, a subclass depends on the implementation details of its superclass for its proper function. The superclass’s implementation may change from release to release, and if it does, the subclass may break, even though its code has not been touched. As a consequence, a subclass must evolve in tandem with its superclass, unless the superclass’s authors have designed and documented it specifically for the purpose of being extended. @@ -12,7 +12,7 @@ Unlike method invocation, inheritance violates encapsulation [Snyder86].In other To make this concrete, let’s suppose we have a program that uses a HashSet. To tune the performance of our program, we need to query the HashSet as to how many elements have been added since it was created (not to be confused with its current size, which goes down when an element is removed). To provide this functionality, we write a HashSet variant that keeps count of the number of attempted element insertions and exports an accessor for this count. The HashSet class contains two methods capable of adding elements, add and addAll, so we override both of these methods: -为了使其更具体一些,让我们假设有一个使用 HashSet 的程序。为了优化程序的性能,我们需要查询 HashSet,以确定自创建以来添加了多少元素(不要与当前的大小混淆,当元素被删除时,当前的大小会递减)。为了提供这个功能,我们编写了一个 HashSet 变量,它记录试图插入的元素数量,并为这个计数导出一个访问。HashSet 类包含两个能够添加元素的方法,add 和 addAll,因此我们覆盖这两个方法: +为了使其更具体一些,让我们假设有一个使用 HashSet 的程序。为了优化程序的性能,我们需要查询 HashSet,以确定自创建以来添加了多少元素(不要与当前的大小混淆,当元素被删除时,当前的大小会递减)。为了提供这个功能,我们编写了一个 HashSet 变量,它记录试图插入的元素数量,并为这个计数导出一个访问。HashSet 类包含两个能够添加元素的方法,add 和 addAll,因此我们覆盖这两个方法: ``` // Broken - Inappropriate use of inheritance! @@ -51,7 +51,7 @@ s.addAll(List.of("Snap", "Crackle", "Pop")); We would expect the getAddCount method to return three at this point, but it returns six. What went wrong? Internally, HashSet’s addAll method is implemented on top of its add method, although HashSet, quite reasonably,does not document this implementation detail. The addAll method in Instrumented-HashSet added three to addCount and then invoked HashSet’s addAll implementation using super.addAll. This in turn invoked the add method, as overridden in InstrumentedHashSet, once for each element. Each of these three invocations added one more to addCount,for a total increase of six: each element added with the addAll method is double-counted. -我们希望 getAddCount 方法此时返回 3,但它返回 6。到底是哪里出了错?在内部,HashSet 的 addAll 方法是在其 add 方法之上实现的,尽管 HashSet 相当合理地没有记录这个实现细节。InstrumentedHashSet 中的 addAll 方法向 addCount 添加了三个元素,然后使用 super.addAll 调用 HashSet 的 addAll 实现。这反过来调用 add 方法(在 InstrumentedHashSet 中被重写过),每个元素一次。这三个调用中的每一个都向 addCount 添加了一个元素,总共增加了 6 个元素:使用 addAll 方法添加的每个元素都被重复计数。 +我们希望 getAddCount 方法此时返回 3,但它返回 6。到底是哪里出了错?在内部,HashSet 的 addAll 方法是在其 add 方法之上实现的,尽管 HashSet 相当合理地没有记录这个实现细节。InstrumentedHashSet 中的 addAll 方法向 addCount 添加了三个元素,然后使用 `super.addAll` 调用 HashSet 的 addAll 实现。这反过来调用 add 方法(在 InstrumentedHashSet 中被重写过),每个元素一次。这三个调用中的每一个都向 addCount 添加了一个元素,总共增加了 6 个元素:使用 addAll 方法添加的每个元素都被重复计数。 We could “fix” the subclass by eliminating its override of the addAll method. While the resulting class would work, it would depend for its proper function on the fact that HashSet’s addAll method is implemented on top of its add method. This “self-use” is an implementation detail, not guaranteed to hold in all implementations of the Java platform and subject to change from release to release. Therefore, the resulting InstrumentedHashSet class would be fragile. @@ -67,11 +67,11 @@ A related cause of fragility in subclasses is that their superclass can acquire Both of these problems stem from overriding methods. You might think that it is safe to extend a class if you merely add new methods and refrain from overriding existing methods. While this sort of extension is much safer, it is not without risk. If the superclass acquires a new method in a subsequent release and you have the bad luck to have given the subclass a method with the same signature and a different return type, your subclass will no longer compile [JLS, 8.4.8.3]. If you’ve given the subclass a method with the same signature and return type as the new superclass method, then you’re now overriding it, so you’re subject to the problems described earlier. Furthermore, it is doubtful that your method will fulfill the contract of the new superclass method, because that contract had not yet been written when you wrote the subclass method. -这两个问题都源于重写方法。你可能认为,如果只添加新方法,并且不覆盖现有方法,那么扩展类是安全的。虽然这种延长会更安全,但也不是没有风险。如果超类在随后的版本中获得了一个新方法,而你不幸给了子类一个具有相同签名和不同返回类型的方法,那么你的子类将不再编译[JLS, 8.4.8.3]。如果给子类一个方法,该方法具有与新超类方法相同的签名和返回类型,那么现在要覆盖它,因此你要面对前面描述的问题。此外,你的方法是否能够完成新的超类方法的契约是值得怀疑的,因为在你编写子类方法时,该契约还没有被写入。 +这两个问题都源于重写方法。你可能认为,如果只添加新方法,并且不覆盖现有方法,那么扩展类是安全的。虽然这种延长会更安全,但也不是没有风险。如果超类在随后的版本中获得了一个新方法,而你不幸给了子类一个具有相同签名和不同返回类型的方法,那么你的子类将不再编译 [JLS, 8.4.8.3]。如果给子类一个方法,该方法具有与新超类方法相同的签名和返回类型,那么现在要覆盖它,因此你要面对前面描述的问题。此外,你的方法是否能够完成新的超类方法的契约是值得怀疑的,因为在你编写子类方法时,该契约还没有被写入。 Luckily, there is a way to avoid all of the problems described above. Instead of extending an existing class, give your new class a private field that references an instance of the existing class. This design is called composition because the existing class becomes a component of the new one. Each instance method in the new class invokes the corresponding method on the contained instance of the existing class and returns the results. This is known as forwarding, and the methods in the new class are known as forwarding methods. The resulting class will be rock solid, with no dependencies on the implementation details of the existing class. Even adding new methods to the existing class will have no impact on the new class. To make this concrete, here’s a replacement for InstrumentedHashSet that uses the composition-and-forwarding approach. Note that the implementation is broken into two pieces, the class itself and a reusable forwarding class, which contains all of the forwarding methods and nothing else: -幸运的是,有一种方法可以避免上述所有问题。与其扩展现有类,不如为新类提供一个引用现有类实例的私有字段。这种设计称为复合,因为现有的类成为新类的一个组件。新类中的每个实例方法调用现有类的包含实例上的对应方法,并返回结果。这称为转发,新类中的方法称为转发方法。生成的类将非常坚固,不依赖于现有类的实现细节。即使向现有类添加新方法,也不会对新类产生影响。为了使其具体化,这里有一个使用复合和转发方法的 InstrumentedHashSet 的替代方法。注意,实现被分成两部分,类本身和一个可重用的转发类,其中包含所有的转发方法,没有其他内容: +幸运的是,有一种方法可以避免上述所有问题。与其扩展现有类,不如为新类提供一个引用现有类实例的私有字段。这种设计称为复合,因为现有的类成为新类的一个组件。新类中的每个实例方法调用现有类的包含实例上的对应方法,并返回结果。这称为转发,新类中的方法称为转发方法。生成的类将非常坚固,不依赖于现有类的实现细节。即使向现有类添加新方法,也不会对新类产生影响。为了使其具体化,这里有一个使用复合和转发方法的 InstrumentedHashSet 的替代方法。注意,实现被分成两部分,类本身和一个可重用的转发类,其中包含所有的转发方法,没有其他内容: ``` // Wrapper class - uses composition in place of inheritance @@ -133,7 +133,7 @@ public class ForwardingSet implements Set { The design of the InstrumentedSet class is enabled by the existence of the Set interface, which captures the functionality of the HashSet class.Besides being robust, this design is extremely flexible. The InstrumentedSet class implements the Set interface and has a single constructor whose argument is also of type Set. In essence, the class transforms one Set into another, adding the instrumentation functionality. Unlike the inheritance-based approach, which works only for a single concrete class and requires a separate constructor for each supported constructor in the superclass,the wrapper class can be used to instrument any Set implementation and will work in conjunction with any preexisting constructor: -InstrumentedSet 类的设计是通过 Set 接口来实现的,这个接口可以捕获 HashSet 类的功能。除了健壮外,这个设计非常灵活。InstrumentedSet 类实现了 Set 接口,有一个构造函数,它的参数也是 Set 类型的。实际上,这个类可以将一个 Set 转换成另一个 Set,添加了 instrumentation 的功能。基于继承的方法只适用于单个具体类,并且需要为超类中每个受支持的构造函数提供单独的构造函数,与此不同的是,包装器类可用于仪器任何集合实现,并将与任何现有构造函数一起工作: +InstrumentedSet 类的设计是通过 Set 接口来实现的,这个接口可以捕获 HashSet 类的功能。除了健壮外,这个设计非常灵活。InstrumentedSet 类实现了 Set 接口,有一个构造函数,它的参数也是 Set 类型的。实际上,这个类可以将一个 Set 转换成另一个 Set,添加了 instrumentation 的功能。基于继承的方法只适用于单个具体类,并且需要为超类中每个受支持的构造函数提供单独的构造函数,与此不同的是,包装器类可用于仪器任何集合实现,并将与任何现有构造函数一起工作: ``` Set times = new InstrumentedSet<>(new TreeSet<>(cmp)); @@ -142,7 +142,7 @@ Set s = new InstrumentedSet<>(new HashSet<>(INIT_CAPACITY)); The InstrumentedSet class can even be used to temporarily instrument a set instance that has already been used without instrumentation: -InstrumentedSet 类甚至还可以用来临时配置一个不用插装就可以使用的 set 实例: +InstrumentedSet 类甚至还可以用来临时配置一个不用插装就可以使用的 set 实例: ``` static void walk(Set dogs) { @@ -157,19 +157,19 @@ InstrumentedSet 类被称为包装类,因为每个 entedset 实例都包含( The disadvantages of wrapper classes are few. One caveat is that wrapper classes are not suited for use in callback frameworks, wherein objects pass selfreferences to other objects for subsequent invocations (“callbacks”). Because a wrapped object doesn’t know of its wrapper, it passes a reference to itself (this) and callbacks elude the wrapper. This is known as the SELF problem [Lieberman86]. Some people worry about the performance impact of forwarding method invocations or the memory footprint impact of wrapper objects. Neither turn out to have much impact in practice. It’s tedious to write forwarding methods, but you have to write the reusable forwarding class for each interface only once, and forwarding classes may be provided for you. For example, Guava provides forwarding classes for all of the collection interfaces [Guava]. -包装类的缺点很少。一个警告是包装类不适合在回调框架中使用,在回调框架中,对象为后续调用(「回调」)将自定义传递给其他对象。因为包装对象不知道它的包装器,所以它传递一个对它自己的引用(这个),回调避开包装器。这就是所谓的自我问题。有些人担心转发方法调用的性能影响或包装器对象的内存占用影响。这两种方法在实践中都没有多大影响。编写转发方法很麻烦,但是你必须只为每个接口编写一次可重用的转发类,而且可能会为你提供转发类。例如,Guava 为所有的集合接口提供了转发类[Guava]。 +包装类的缺点很少。一个警告是包装类不适合在回调框架中使用,在回调框架中,对象为后续调用(「回调」)将自定义传递给其他对象。因为包装对象不知道它的包装器,所以它传递一个对它自己的引用(this),回调避开包装器。这就是所谓的自我问题。有些人担心转发方法调用的性能影响或包装器对象的内存占用影响。这两种方法在实践中都没有多大影响。编写转发方法很麻烦,但是你必须只为每个接口编写一次可重用的转发类,而且可能会为你提供转发类。例如,Guava 为所有的集合接口提供了转发类[Guava]。 Inheritance is appropriate only in circumstances where the subclass really is a subtype of the superclass. In other words, a class B should extend a class A only if an “is-a” relationship exists between the two classes. If you are tempted to have a class B extend a class A, ask yourself the question: Is every B really an A?If you cannot truthfully answer yes to this question, B should not extend A. If the answer is no, it is often the case that B should contain a private instance of A and expose a different API: A is not an essential part of B, merely a detail of its implementation. -只有在子类确实是超类的子类型的情况下,继承才合适。换句话说,只有当两个类之间存在“is-a”关系时,类 B 才应该扩展类 a。如果你想让 B 类扩展 a 类,那就问问自己:每个 B 都是 a 吗?如果你不能如实回答是的这个问题,B 不应该延长 a,如果答案是否定的,通常情况下,B 应该包含一个私人的实例,让不同的 API:不是 B 的一个重要组成部分,只是一个细节的实现。 +只有在子类确实是超类的子类型的情况下,继承才合适。换句话说,只有当两个类之间存在「is-a」关系时,类 B 才应该扩展类 a。如果你想让 B 类扩展 a 类,那就问问自己:每个 B 都是 a 吗?如果你不能如实回答是的这个问题,B 不应该延长 a,如果答案是否定的,通常情况下,B 应该包含一个私人的实例,让不同的 API:不是 B 的一个重要组成部分,只是一个细节的实现。 There are a number of obvious violations of this principle in the Java platform libraries. For example, a stack is not a vector, so Stack should not extend Vector. Similarly, a property list is not a hash table, so Properties should not extend Hashtable. In both cases, composition would have been preferable. -在 Java 平台库中有许多明显违反这一原则的地方。例如,堆栈不是向量,因此堆栈不应该扩展向量。类似地,属性列表不是哈希表,因此属性不应该扩展哈希表。在这两种情况下,复合都是可取的。 +在 Java 库中有许多明显违反这一原则的地方。例如,堆栈不是向量,因此堆栈不应该扩展向量。类似地,属性列表不是 hash 表,因此属性不应该扩展 hash 表。在这两种情况下,复合都是可取的。 If you use inheritance where composition is appropriate, you needlessly expose implementation details. The resulting API ties you to the original implementation, forever limiting the performance of your class. More seriously,by exposing the internals you let clients access them directly. At the very least, it can lead to confusing semantics. For example, if p refers to a Properties instance, then p.getProperty(key) may yield different results from p.get(key): the former method takes defaults into account, while the latter method, which is inherited from Hashtable, does not. Most seriously, the client may be able to corrupt invariants of the subclass by modifying the superclass directly. In the case of Properties, the designers intended that only strings be allowed as keys and values, but direct access to the underlying Hashtable allows this invariant to be violated. Once violated, it is no longer possible to use other parts of the Properties API (load and store). By the time this problem was discovered, it was too late to correct it because clients depended on the use of non-string keys and values. -如果在复合合适的地方使用继承,就不必要地公开实现细节。生成的 API 将你与原始实现绑定在一起,永远限制了类的性能。更严重的是,通过公开内部组件,你可以让客户端直接访问它们。至少,它会导致语义混乱。例如,如果 p 引用了一个属性实例,那么 p. getproperty (key)可能会产生与 p.get(key)不同的结果:前者考虑了默认值,而后者(从 Hashtable 继承而来)则不会。最严重的是,客户端可以通过直接修改超类来破坏子类的不变量。对于属性,设计者希望只允许字符串作为键和值,但是直接访问底层哈希表允许违反这个不变量。一旦违反,就不再可能使用 Properties API 的其他部分(加载和存储)。当发现这个问题时,已经太晚了,无法纠正它,因为客户端依赖于非字符串键和值的使用。 +如果在复合合适的地方使用继承,就不必要地公开实现细节。生成的 API 将你与原始实现绑定在一起,永远限制了类的性能。更严重的是,通过公开内部组件,你可以让客户端直接访问它们。至少,它会导致语义混乱。例如,如果 p 引用了一个属性实例,那么 `p.getProperty(key)` 可能会产生与 `p.get(key)` 不同的结果:前者考虑了默认值,而后者(从 Hashtable 继承而来)则不会。最严重的是,客户端可以通过直接修改超类来破坏子类的不变量。对于属性,设计者希望只允许字符串作为键和值,但是直接访问底层 hash 表允许违反这个不变量。一旦违反,就不再可能使用 Properties API 的其他部分(加载和存储)。当发现这个问题时,已经太晚了,无法纠正它,因为客户端依赖于非字符串键和值的使用。 There is one last set of questions you should ask yourself before deciding to use inheritance in place of composition. Does the class that you contemplate extending have any flaws in its API? If so, are you comfortable propagating those flaws into your class’s API? Inheritance propagates any flaws in the superclass’s API, while composition lets you design a new API that hides these flaws. diff --git a/Chapter-4/Chapter-4-Item-19-Design-and-document-for-inheritance-or-else-prohibit-it.md b/Chapter-4/Chapter-4-Item-19-Design-and-document-for-inheritance-or-else-prohibit-it.md index 10a752b..a40f0c0 100644 --- a/Chapter-4/Chapter-4-Item-19-Design-and-document-for-inheritance-or-else-prohibit-it.md +++ b/Chapter-4/Chapter-4-Item-19-Design-and-document-for-inheritance-or-else-prohibit-it.md @@ -2,23 +2,23 @@ ### Item 19: Design and document for inheritance or else prohibit it(继承要设计良好并且具有文档,否则禁止使用) -[Item-18](https://github.com/clxering/Effective-Java-3rd-edition-Chinese-English-bilingual/blob/master/Chapter-4/Chapter-4-Item-18-Favor-composition-over-inheritance.md) alerted you to the dangers of subclassing a “foreign” class that was not designed and documented for inheritance. So what does it mean for a class to be designed and documented for inheritance? +Item 18 alerted you to the dangers of subclassing a “foreign” class that was not designed and documented for inheritance. So what does it mean for a class to be designed and documented for inheritance? -第 18 项提醒你注意子类化不是为继承设计和文档化的「外部」类的危险。那么,为继承而设计和文档化的类意味着什么呢? +[Item-18](https://github.com/clxering/Effective-Java-3rd-edition-Chinese-English-bilingual/blob/master/Chapter-4/Chapter-4-Item-18-Favor-composition-over-inheritance.md) 提醒你注意子类化不是为继承设计和文档化的「外部」类的危险。那么,为继承而设计和文档化的类意味着什么呢? First, the class must document precisely the effects of overriding any method.In other words, **the class must document its self-use of overridable methods.** For each public or protected method, the documentation must indicate which overridable methods the method invokes, in what sequence, and how the results of each invocation affect subsequent processing. (By overridable, we mean nonfinal and either public or protected.) More generally, a class must document any circumstances under which it might invoke an overridable method. For example, invocations might come from background threads or static initializers. -首先,类必须精确地(在文档中)记录覆盖任何方法的效果。换句话说,类必须(在文档中)记录它对可覆盖方法的自用。对于每个公共或受保护的方法,文档必须指出方法调用的可覆盖方法、调用顺序以及每次调用的结果如何影响后续处理(过程)。(可覆盖的意思是非 final 的,公共的或受保护的。)更一般地说,类必须记录它可能调用可覆盖方法的任何情况。例如,调用可能来自后台线程或静态初始化器。 +首先,类必须精确地在文档中记录覆盖任何方法的效果。换句话说,类必须在文档中记录它对可覆盖方法的自用。对于每个公共或受保护的方法,文档必须指出方法调用的可覆盖方法、调用顺序以及每次调用的结果如何影响后续处理过程。(可覆盖的意思是非 final 的,公共的或受保护的。)更一般地说,类必须记录它可能调用可覆盖方法的任何情况。例如,调用可能来自后台线程或静态初始化器。 A method that invokes overridable methods contains a description of these invocations at the end of its documentation comment. The description is in a special section of the specification, labeled “Implementation Requirements,”which is generated by the Javadoc tag @implSpec. This section describes the inner workings of the method. Here’s an example, copied from the specification for java.util.AbstractCollection: -调用可覆盖方法的方法在其文档注释末尾(应)包含这些调用的描述。描述在规范的一个特殊部分中,标记为「实现需求」,它由 Javadoc 标签@implSpec 生成。本节描述该方法的内部工作方式。下面是一个示例,复制自 java.util.AbstractCollection 规范: +调用可覆盖方法的方法在其文档注释末尾应包含这些调用的描述。描述在规范的一个特殊部分中,标记为「实现需求」,它由 Javadoc 标签 `@implSpec` 生成。本节描述该方法的内部工作方式。下面是一个示例,复制自 `java.util.AbstractCollection` 规范: > public boolean remove(Object o) > Removes a single instance of the specified(v. 指定;详细说明,adj. 规定的;) element from this collection, if it is present (optional operation). More formally, removes an element e such that Objects.equals(o, e), if this collection contains one or more such elements. Returns true if this collection contained the specified element (or equivalently, if this collection changed as a result of the call). -从此集合中移除指定元素的单个实例,如果存在(可选操作)。更正式地说,如果此集合包含一个或多个这样的元素,则删除元素 e,使得 Objects.equals(o, e),如果此 collection 包含指定的元素,则返回 true(或等效地,如果此集合因调用而更改)。 +从此集合中移除指定元素的单个实例,如果存在(可选操作)。更正式地说,如果此集合包含一个或多个这样的元素,则删除元素 e,使得 `Objects.equals(o, e)`,如果此 collection 包含指定的元素,则返回 true(或等效地,如果此集合因调用而更改)。 > Implementation Requirements: This implementation iterates over the collection looking for the specified element. If it finds the element, it removes the element from the collection using the iterator’s remove method. Note that this implementation throws an UnsupportedOperationException if the iterator returned by this collection’s iterator method does not implement the remove method and this collection contains the specified object. @@ -26,7 +26,7 @@ A method that invokes overridable methods contains a description of these invoca This documentation leaves no doubt that overriding the iterator method will affect the behavior of the remove method. It also describes exactly how the behavior of the Iterator returned by the iterator method will affect the behavior of the remove method. Contrast this to the situation in Item 18, where the programmer subclassing HashSet simply could not say whether overriding the add method would affect the behavior of the addAll method. -这篇文档无疑说明了重写迭代器方法将影响 remove 方法的行为。它还准确地描述了迭代器方法返回的迭代器的行为将如何影响 remove 方法的行为。与第 18 项中的情况相反,在第 18 项中,程序员子类化 HashSet 不能简单地说覆盖 add 方法是否会影响 addAll 方法的行为。 +这篇文档无疑说明了重写迭代器方法将影响 remove 方法的行为。它还准确地描述了迭代器方法返回的迭代器的行为将如何影响 remove 方法的行为。与 [Item-18](https://github.com/clxering/Effective-Java-3rd-edition-Chinese-English-bilingual/blob/master/Chapter-4/Chapter-4-Item-18-Favor-composition-over-inheritance.md) 中的情况相反,在 [Item-18](https://github.com/clxering/Effective-Java-3rd-edition-Chinese-English-bilingual/blob/master/Chapter-4/Chapter-4-Item-18-Favor-composition-over-inheritance.md) 中,程序员子类化 HashSet 不能简单地说覆盖 add 方法是否会影响 addAll 方法的行为。 But doesn’t this violate the dictum that good API documentation should describe what a given method does and not how it does it? Yes, it does! This is an unfortunate consequence of the fact that inheritance violates encapsulation. To document a class so that it can be safely subclassed, you must describe implementation details that should otherwise be left unspecified. @@ -34,17 +34,17 @@ But doesn’t this violate the dictum that good API documentation should describ The @implSpec tag was added in Java 8 and used heavily in Java 9. This tag should be enabled by default, but as of Java 9, the Javadoc utility still ignores it unless you pass the command line switch -tag "apiNote: a :API Note:". -@implSpec 标记在 Java 8 中添加,在 Java 9 中大量使用。默认情况下应该启用这个标记,但是在 Java 9 中,Javadoc 实用程序仍然忽略它,除非传递命令行开关-tag "apiNote: a :API Note:"。 +`@implSpec` 标记在 Java 8 中添加,在 Java 9 中大量使用。默认情况下应该启用这个标记,但是在 Java 9 中,Javadoc 实用程序仍然忽略它,除非传递命令行开关 `-tag "apiNote: a :API Note:"`。 -Designing for inheritance involves more than just documenting patterns of self-use. To allow programmers to write efficient subclasses without undue pain, a class may have to provide hooks into its internal workings in the form of judiciously chosen protected methods or, in rare instances, protected fields.For example, consider the removeRange method from java.util.AbstractList: +Designing for inheritance involves more than just documenting patterns of self-use. To allow programmers to write efficient subclasses without undue pain, a class may have to provide hooks into its internal workings in the form of judiciously chosen protected methods or, in rare instances, protected fields. For example, consider the removeRange method from java.util.AbstractList: -为继承而设计不仅仅是记录自使用的模式。为了允许程序员编写高效的子类而不受不必要的痛苦,类可能必须以明智地选择受保护的方法或(在很少的情况下)受保护的字段的形式为其内部工作提供挂钩。例如,考虑来自 java.util.AbstractList 的 removeRange 方法: +为继承而设计不仅仅是记录自使用的模式。为了允许程序员编写高效的子类而不受不必要的痛苦,类可能必须以明智地选择受保护的方法或(在很少的情况下)受保护的字段的形式为其内部工作提供挂钩。例如,考虑来自 `java.util.AbstractList` 的 removeRange 方法: > protected void removeRange(int fromIndex, int toIndex) > Removes from this list all of the elements whose index is between fromIndex, inclusive, and toIndex, exclusive. Shifts any succeeding elements to the left (reduces their index). This call shortens the list by (toIndex - fromIndex) elements. (If toIndex == fromIndex,this operation has no effect.) -从这个列表中删除所有索引位于 fromIndex(包含索引)和 toIndex(独占索引)之间的元素。将任何后续元素移到左边(减少其索引)。这个调用使用(toIndex - fromIndex)元素缩短列表。(如果 toIndex == fromIndex,此操作无效。) +从这个列表中删除所有索引位于 fromIndex(包含索引)和 toIndex(独占索引)之间的元素。将任何后续元素移到左边(减少其索引)。这个调用使用 `(toIndex - fromIndex)` 元素缩短列表。(如果 toIndex == fromIndex,此操作无效。) > This method is called by the clear operation on this list and its sublists.Overriding this method to take advantage of the internals of the list implementation can substantially improve the performance of the clear operation on this list and its sublists. @@ -52,7 +52,7 @@ Designing for inheritance involves more than just documenting patterns of self-u > Implementation Requirements: This implementation gets a list iterator positioned before fromIndex and repeatedly calls ListIterator.next followed by ListIterator.remove, until the entire range has been removed. Note: If ListIterator.remove requires linear time, this implementation requires quadratic time. -实现需求:该实现获取位于 fromIndex 之前的列表迭代器,并依次重复调用 ListIterator.next 和 ListIterator.remove,直到删除整个范围(的内容)。注意:如果 ListIterator.remove 需要线性时间,这个实现需要平方级的时间。 +实现需求:该实现获取位于 fromIndex 之前的列表迭代器,并依次重复调用 `ListIterator.next` 和 `ListIterator.remove`,直到删除整个范围的内容。注意:如果 `ListIterator.remove` 需要线性时间,这个实现需要平方级的时间。 > Parameters: @@ -72,7 +72,7 @@ This method is of no interest to end users of a List implementation. It is provi So how do you decide what protected members to expose when you design a class for inheritance? Unfortunately, there is no magic bullet. The best you can do is to think hard, take your best guess, and then test it by writing subclasses.You should expose as few protected members as possible because each one represents a commitment to an implementation detail. On the other hand, you must not expose too few because a missing protected member can render a class practically unusable for inheritance. -那么,在为继承设计类时,如何决定要公开哪些受保护的成员呢?不幸的是,没有灵丹妙药。你能做的最好的事情就是认真思考,做出最好的猜测,然后通过编写子类来测试它。你应该尽可能少地公开受保护的成员,因为每个成员都表示对实现细节的承诺。另一方面,你不能公开太多,因为缺少受保护的成员会导致类实际上无法用于继承。 +那么,在为继承设计类时,如何决定要公开哪些受保护的成员呢?不幸的是,没有灵丹妙药。你能做的最好的事情就是认真思考,做出最好的猜测,然后通过编写子类来测试它。你应该尽可能少地公开受保护的成员,因为每个成员都表示对实现细节的承诺。另一方面,你不能公开太多,因为缺少受保护的成员会导致类实际上无法用于继承。 **The only way to test a class designed for inheritance is to write subclasses.** If you omit a crucial protected member, trying to write a subclass will make the omission painfully obvious. Conversely, if several subclasses are written and none uses a protected member, you should probably make it private. Experience shows that three subclasses are usually sufficient to test an extendable class. One or more of these subclasses should be written by someone other than the superclass author. @@ -88,7 +88,7 @@ Also, note that the special documentation required for inheritance clutters up n There are a few more restrictions that a class must obey to allow inheritance.**Constructors must not invoke overridable methods,** directly or indirectly. If you violate this rule, program failure will result. The superclass constructor runs before the subclass constructor, so the overriding method in the subclass will get invoked before the subclass constructor has run. If the overriding method depends on any initialization performed by the subclass constructor, the method will not behave as expected. To make this concrete, here’s a class that violates this rule: -为了允许继承,类必须遵守更多的限制。**构造函数不能直接或间接调用可重写的方法。** 如果你违反了这个规则,程序就会失败。超类构造函数在子类构造函数之前运行,因此在子类构造函数运行之前将调用子类中的覆盖方法。如果重写方法依赖于子类构造函数执行的任何初始化,则该方法的行为将不像预期的那样。为了使其具体化,下面是一个违反此规则的类: +为了允许继承,类必须遵守更多的限制。**构造函数不能直接或间接调用可重写的方法。** 如果你违反了这个规则,程序就会失败。超类构造函数在子类构造函数之前运行,因此在子类构造函数运行之前将调用子类中的覆盖方法。如果重写方法依赖于子类构造函数执行的任何初始化,则该方法的行为将不像预期的那样。为了使其具体化,下面是一个违反此规则的类: ``` public class Super { @@ -134,7 +134,7 @@ Note that it is safe to invoke private methods, final methods, and static method The Cloneable and Serializable interfaces present special difficulties when designing for inheritance. It is generally not a good idea for a class designed for inheritance to implement either of these interfaces because they place a substantial burden on programmers who extend the class. There are,however, special actions that you can take to allow subclasses to implement these interfaces without mandating that they do so. These actions are described in Item 13 and Item 86. -可克隆和可序列化的接口在设计继承时存在特殊的困难。对于为继承而设计的类来说,实现这两种接口都不是一个好主意,因为它们给扩展类的程序员带来了沉重的负担。但是,你可以采取一些特殊的操作来允许子类实现这些接口,而无需强制它们这样做。项目 13 和项目 86 叙述了这些行动。 +可克隆和可序列化的接口在设计继承时存在特殊的困难。对于为继承而设计的类来说,实现这两种接口都不是一个好主意,因为它们给扩展类的程序员带来了沉重的负担。但是,你可以采取一些特殊的操作来允许子类实现这些接口,而无需强制它们这样做。[Item-13](https://github.com/clxering/Effective-Java-3rd-edition-Chinese-English-bilingual/blob/master/Chapter-3/Chapter-3-Item-13-Override-clone-judiciously.md) 和 [Item-86](https://github.com/clxering/Effective-Java-3rd-edition-Chinese-English-bilingual/blob/master/Chapter-12/Chapter-12-Item-86-Implement-Serializable-with-great-caution.md) 叙述了这些行动。 If you do decide to implement either Cloneable or Serializable in a class that is designed for inheritance, you should be aware that because the clone and readObject methods behave a lot like constructors, a similar restriction applies: neither clone nor readObject may invoke an overridable method, directly or indirectly. In the case of readObject, the overriding method will run before the subclass’s state has been deserialized. In the case of clone, the overriding method will run before the subclass’s clone method has a chance to fix the clone’s state. In either case, a program failure is likely to follow. In the case of clone, the failure can damage the original object as well as the clone. This can happen, for example, if the overriding method assumes it is modifying the clone’s copy of the object’s deep structure, but the copy hasn’t been made yet. @@ -146,7 +146,7 @@ Finally, if you decide to implement Serializable in a class designed for inherit By now it should be apparent that designing a class for inheritance requires great effort and places substantial limitations on the class. This is not a decision to be undertaken lightly. There are some situations where it is clearly the right thing to do, such as abstract classes, including skeletal implementations of interfaces (Item 20). There are other situations where it is clearly the wrong thing to do, such as immutable classes (Item 17). -到目前为止,显然为继承而设计一个类需要付出很大的努力,并且对类有很大的限制。这不是一个可以轻易作出的决定。在某些情况下,这样做显然是正确的,例如抽象类,包括接口的骨架实现(项目 20)。还有一些情况显然是错误的,比如不可变类([Item-17](https://github.com/clxering/Effective-Java-3rd-edition-Chinese-English-bilingual/blob/master/Chapter-4/Chapter-4-Item-17-Minimize-mutability.md))。 +到目前为止,显然为继承而设计一个类需要付出很大的努力,并且对类有很大的限制。这不是一个可以轻易作出的决定。在某些情况下,这样做显然是正确的,例如抽象类,包括接口的骨架实现([Item-20](https://github.com/clxering/Effective-Java-3rd-edition-Chinese-English-bilingual/blob/master/Chapter-4/Chapter-4-Item-20-Prefer-interfaces-to-abstract-classes.md))。还有一些情况显然是错误的,比如不可变类([Item-17](https://github.com/clxering/Effective-Java-3rd-edition-Chinese-English-bilingual/blob/master/Chapter-4/Chapter-4-Item-17-Minimize-mutability.md))。 But what about ordinary concrete classes? Traditionally, they are neither final nor designed and documented for subclassing, but this state of affairs is dangerous. Each time a change is made in such a class, there is a chance that subclasses extending the class will break. This is not just a theoretical problem.It is not uncommon to receive subclassing-related bug reports after modifying the internals of a nonfinal concrete class that was not designed and documented for inheritance. @@ -154,11 +154,11 @@ But what about ordinary concrete classes? Traditionally, they are neither final The best solution to this problem is to prohibit subclassing in classes that are not designed and documented to be safely subclassed. There are two ways to prohibit subclassing. The easier of the two is to declare the class final. The alternative is to make all the constructors private or package-private and to add public static factories in place of the constructors. This alternative, which provides the flexibility to use subclasses internally, is discussed in Item 17. Either approach is acceptable. -这个问题的最佳解决方案是禁止在没有设计和文档记录的类中进行子类化。有两种方法可以禁止子类化。两者中比较容易的是声明类 final。另一种方法是将所有构造函数变为私有或包私有,并在构造函数的位置添加公共静态工厂。这个替代方案提供了内部使用子类的灵活性,在[Item-17](https://github.com/clxering/Effective-Java-3rd-edition-Chinese-English-bilingual/blob/master/Chapter-4/Chapter-4-Item-17-Minimize-mutability.md)中进行了讨论。两种方法都可以接受。 +这个问题的最佳解决方案是禁止在没有设计和文档记录的类中进行子类化。有两种方法可以禁止子类化。两者中比较容易的是声明类 final。另一种方法是将所有构造函数变为私有或包私有,并在构造函数的位置添加公共静态工厂。这个替代方案提供了内部使用子类的灵活性,在 [Item-17](https://github.com/clxering/Effective-Java-3rd-edition-Chinese-English-bilingual/blob/master/Chapter-4/Chapter-4-Item-17-Minimize-mutability.md) 中进行了讨论。两种方法都可以接受。 This advice may be somewhat controversial because many programmers have grown accustomed to subclassing ordinary concrete classes to add facilities such as instrumentation, notification, and synchronization or to limit functionality. If a class implements some interface that captures its essence, such as Set, List, or Map, then you should feel no compunction about prohibiting subclassing. The wrapper class pattern, described in Item 18, provides a superior alternative to inheritance for augmenting the functionality. -这个建议可能有点争议,因为许多程序员已经习惯了子类化普通的具体类,以添加工具、通知和同步等功能或限制功能。如果一个类实现了某个接口,该接口捕获了它的本质,例如 Set、List 或 Map,那么你不应该对禁止子类化感到内疚。在[Item-18](https://github.com/clxering/Effective-Java-3rd-edition-Chinese-English-bilingual/blob/master/Chapter-4/Chapter-4-Item-18-Favor-composition-over-inheritance.md)中描述的包装器类模式提供了一种优于继承的方法来增强功能。 +这个建议可能有点争议,因为许多程序员已经习惯了子类化普通的具体类,以添加工具、通知和同步等功能或限制功能。如果一个类实现了某个接口,该接口捕获了它的本质,例如 Set、List 或 Map,那么你不应该对禁止子类化感到内疚。在 [Item-18](https://github.com/clxering/Effective-Java-3rd-edition-Chinese-English-bilingual/blob/master/Chapter-4/Chapter-4-Item-18-Favor-composition-over-inheritance.md) 中描述的包装器类模式提供了一种优于继承的方法来增强功能。 If a concrete class does not implement a standard interface, then you may inconvenience some programmers by prohibiting inheritance. If you feel that you must allow inheritance from such a class, one reasonable approach is to ensure that the class never invokes any of its overridable methods and to document this fact. In other words, eliminate the class’s self-use of overridable methods entirely. In doing so, you’ll create a class that is reasonably safe to subclass. Overriding a method will never affect the behavior of any other method. diff --git a/Chapter-4/Chapter-4-Item-20-Prefer-interfaces-to-abstract-classes.md b/Chapter-4/Chapter-4-Item-20-Prefer-interfaces-to-abstract-classes.md index 839a794..47ca58a 100644 --- a/Chapter-4/Chapter-4-Item-20-Prefer-interfaces-to-abstract-classes.md +++ b/Chapter-4/Chapter-4-Item-20-Prefer-interfaces-to-abstract-classes.md @@ -4,15 +4,15 @@ Java has two mechanisms to define a type that permits multiple implementations: interfaces and abstract classes. Since the introduction of default methods for interfaces in Java 8 [JLS 9.4.3], both mechanisms allow you to provide implementations for some instance methods. A major difference is that to implement the type defined by an abstract class, a class must be a subclass of the abstract class. Because Java permits only single inheritance, this restriction on abstract classes severely constrains their use as type definitions.Any class that defines all the required methods and obeys the general contract is permitted to implement an interface, regardless of where the class resides in the class hierarchy. -Java 有两种机制来定义允许多种实现的类型:接口和抽象类。由于 Java 8 [JLS 9.4.3]中引入了接口的默认方法,这两种机制都允许你为一些实例方法提供实现。一个主要区别是,(一个类)要实现抽象类定义的类型,(该)类必须是抽象类的子类。因为 Java 只允许单一继承,所以这种对抽象类的限制严重制约了它们作为类型定义的使用。任何定义了所有必需的方法并遵守通用约定的类都允许实现接口,而不管(该)类驻留在类层次结构中何处。 +Java 有两种机制来定义允许多种实现的类型:接口和抽象类。由于 Java 8 [JLS 9.4.3]中引入了接口的默认方法,这两种机制都允许你为一些实例方法提供实现。一个主要区别是,一个类要实现抽象类定义的类型,该类必须是抽象类的子类。因为 Java 只允许单一继承,所以这种对抽象类的限制严重制约了它们作为类型定义的使用。任何定义了所有必需的方法并遵守通用约定的类都允许实现接口,而不管该类驻留在类层次结构中何处。 ***译注:第一段可拆分出有关抽象类和接口的描述*** -***1、抽象类的局限:(一个类)要实现抽象类定义的类型,(该)类必须是抽象类的子类。因为 Java 只允许单一继承,所以这种对抽象类的限制严重制约了它们作为类型定义的使用。*** +***1、抽象类的局限:一个类要实现抽象类定义的类型,该类必须是抽象类的子类。因为 Java 只允许单一继承,所以这种对抽象类的限制严重制约了它们作为类型定义的使用。*** -***2、接口的优点:任何定义了所有必需的方法并遵守通用约定的类都允许实现接口,而不管(该)类驻留在类层次结构中何处。*** +***2、接口的优点:任何定义了所有必需的方法并遵守通用约定的类都允许实现接口,而不管该类驻留在类层次结构中何处。*** -**Existing classes can easily be retrofitted(翻新) to implement a new interface.** All you have to do is to add the required methods, if they don’t yet exist, and to add an implements clause to the class declaration. For example, many existing classes were retrofitted to implement the Comparable, Iterable, and Autocloseable interfaces when they were added to the platform. Existing classes cannot, in general, be retrofitted to extend a new abstract class. If you want to have two classes extend the same abstract class, you have to place it high up in the type hierarchy where it is an ancestor of both classes. Unfortunately,this can cause great collateral damage to the type hierarchy, forcing all descendants of the new abstract class to subclass it, whether or not it is appropriate. +**Existing classes can easily be retrofitted to implement a new interface.** All you have to do is to add the required methods, if they don’t yet exist, and to add an implements clause to the class declaration. For example, many existing classes were retrofitted to implement the Comparable, Iterable, and Autocloseable interfaces when they were added to the platform. Existing classes cannot, in general, be retrofitted to extend a new abstract class. If you want to have two classes extend the same abstract class, you have to place it high up in the type hierarchy where it is an ancestor of both classes. Unfortunately,this can cause great collateral damage to the type hierarchy, forcing all descendants of the new abstract class to subclass it, whether or not it is appropriate. **可以很容易地对现有类进行改造,以实现新的接口。** 你所要做的就是添加所需的方法(如果它们还不存在的话),并向类声明中添加一个 implements 子句。例如,许多现有的类在添加到平台时进行了修改,以实现 Comparable、Iterable 和 Autocloseable 接口。一般来说,现有的类不能被修改以扩展新的抽象类。如果你想让两个类扩展同一个抽象类,你必须把它放在类型层次结构的高层,作为两个类的祖先。不幸的是,这可能会对类型层次结构造成巨大的附带损害,迫使新抽象类的所有后代对其进行子类化,无论它是否合适。 @@ -26,22 +26,22 @@ Java 有两种机制来定义允许多种实现的类型:接口和抽象类。 ``` public interface Singer { - AudioClip sing(Song s); + AudioClip sing(Song s); } public interface Songwriter { - Song compose(int chartPosition); + Song compose(int chartPosition); } ``` In real life, some singers are also songwriters. Because we used interfaces rather than abstract classes to define these types, it is perfectly permissible for a single class to implement both Singer and Songwriter. In fact, we can define a third interface that extends both Singer and Songwriter and adds new methods that are appropriate to the combination: -在现实生活中,一些歌手也是词曲作者。因为我们使用接口而不是抽象类来定义这些类型,所以完全允许单个类(同时)实现歌手和词曲作者。事实上,我们可以定义第三个接口,扩展歌手和词曲作者,并添加适合(这种)组合的新方法: +在现实生活中,一些歌手也是词曲作者。因为我们使用接口而不是抽象类来定义这些类型,所以完全允许单个类同时实现歌手和词曲作者。事实上,我们可以定义第三个接口,扩展歌手和词曲作者,并添加适合这种组合的新方法: ``` public interface SingerSongwriter extends Singer, Songwriter { - AudioClip strum(); - void actSensitive(); + AudioClip strum(); + void actSensitive(); } ``` @@ -51,11 +51,11 @@ You don’t always need this level of flexibility, but when you do, interfaces a Interfaces enable safe, powerful functionality(n. 功能;[数] 泛函性,函数性) enhancements via the wrapper class idiom (Item 18). If you use abstract classes to define types, you leave the programmer who wants to add functionality with no alternative but inheritance. The resulting classes are less powerful and more fragile than wrapper classes. -通过[Item-18](https://github.com/clxering/Effective-Java-3rd-edition-Chinese-English-bilingual/blob/master/Chapter-4/Chapter-4-Item-18-Favor-composition-over-inheritance.md)介绍的包装类,接口能够支持安全、强大的功能增强。如果你使用抽象类来定义类型,那么你将让希望添加功能的程序员除了继承之外别无选择。最终生成的类不如包装类强大,也更脆弱。 +通过 [Item-18](https://github.com/clxering/Effective-Java-3rd-edition-Chinese-English-bilingual/blob/master/Chapter-4/Chapter-4-Item-18-Favor-composition-over-inheritance.md) 介绍的包装类,接口能够支持安全、强大的功能增强。如果你使用抽象类来定义类型,那么你将让希望添加功能的程序员除了继承之外别无选择。最终生成的类不如包装类强大,也更脆弱。 When there is an obvious implementation of an interface method in terms of other interface methods, consider providing implementation assistance to programmers in the form of a default method. For an example of this technique, see the removeIf method on page 104. If you provide default methods, be sure to document them for inheritance using the @implSpec Javadoc tag (Item 19). -当接口方法的其他接口方法有明显的实现时,考虑以默认方法的形式为程序员提供实现帮助。有关此技术的示例,请参阅第 104 页的 removeIf 方法。如果你提供了默认方法,请使用 **@implSpec** 标签,并确保在文档中记录他们的继承关系([Item-19](https://github.com/clxering/Effective-Java-3rd-edition-Chinese-English-bilingual/blob/master/Chapter-4/Chapter-4-Item-19-Design-and-document-for-inheritance-or-else-prohibit-it.md))。 +当接口方法的其他接口方法有明显的实现时,考虑以默认方法的形式为程序员提供实现帮助。有关此技术的示例,请参阅第 104 页的 removeIf 方法。如果你提供了默认方法,请使用 `@implSpec` 标签,并确保在文档中记录他们的继承关系([Item-19](https://github.com/clxering/Effective-Java-3rd-edition-Chinese-English-bilingual/blob/master/Chapter-4/Chapter-4-Item-19-Design-and-document-for-inheritance-or-else-prohibit-it.md))。 There are limits on how much implementation assistance(n. 援助,帮助;辅助设备) you can provide with default methods. Although many interfaces specify the behavior of Object methods such as equals and hashCode, you are not permitted to provide default methods for them. Also, interfaces are not permitted to contain instance fields or nonpublic static members (with the exception of private static methods). Finally, you can’t add default methods to an interface that you don’t control. @@ -63,7 +63,7 @@ There are limits on how much implementation assistance(n. 援助,帮助; You can, however, combine the advantages of interfaces and abstract classes by providing an abstract skeletal implementation class to go with an interface. The interface defines the type, perhaps providing some default methods, while the skeletal implementation class implements the remaining non-primitive interface methods atop the primitive interface methods. Extending a skeletal implementation takes most of the work out of implementing an interface. This is the Template Method pattern [Gamma95]. -但是,你可以通过提供一个抽象骨架实现类来结合接口和抽象类的优点。接口定义了类型,可能提供了一些默认方法,而骨架实现类在基本接口方法之上实现了其余的非基本接口方法。扩展骨架实现需要完成实现接口的大部分工作。这是模板方法模式[Gamma95]。 +但是,你可以通过提供一个抽象骨架实现类来结合接口和抽象类的优点。接口定义了类型,可能提供了一些默认方法,而骨架实现类在基本接口方法之上实现了其余的非基本接口方法。扩展骨架实现需要完成实现接口的大部分工作。这是模板方法模式 [Gamma95]。 By convention, skeletal implementation classes are called AbstractInterface, where Interface is the name of the interface they implement. For example, the Collections Framework provides a skeletal implementation to go along with each main collection interface: AbstractCollection, AbstractSet, AbstractList, and AbstractMap. Arguably it would have made sense to call them SkeletalCollection, SkeletalSet, SkeletalList, and SkeletalMap, but the Abstract convention is now firmly established. When properly designed, skeletal implementations (whether a separate abstract class, or consisting solely of default methods on an interface) can make it very easy for programmers to provide their own implementations of an interface. For example, here’s a static factory method containing a complete, fully functional List implementation atop AbstractList: @@ -106,53 +106,53 @@ The beauty of skeletal implementation classes is that they provide all of the im Writing a skeletal implementation is a relatively simple, if somewhat tedious, process. First, study the interface and decide which methods are the primitives in terms of which the others can be implemented. These primitives will be the abstract methods in your skeletal implementation. Next, provide default methods in the interface for all of the methods that can be implemented directly atop the primitives, but recall that you may not provide default methods for Object methods such as equals and hashCode. If the primitives and default methods cover the interface, you’re done, and have no need for a skeletal implementation class. Otherwise, write a class declared to implement the interface, with implementations of all of the remaining interface methods. The class may contain any nonpublic fields ands methods appropriate to the task. -编写一个骨架实现是一个相对简单的过程,尽管有点乏味。首先,研究接口并决定哪些方法是基本方法,以便其他方法可以(根据它们)实现。这些基本(方法)将是你的骨架实现中的抽象方法。接下来,在接口中为所有可以直接在原语之上实现的方法提供默认方法,但请记住,你可能不会为诸如 equals 和 hashCode 之类的对象方法提供默认方法。如果原语和默认方法覆盖了接口,那么就完成了,不需要一个骨架实现类。否则,编写一个声明为实现接口的类,并实现所有剩余的接口方法。该类可能包含任何适合于任务的非公共字段和方法。 +编写一个骨架实现是一个相对简单的过程,尽管有点乏味。首先,研究接口并决定哪些方法是基本方法,以便其他方法可以根据它们实现。这些基本方法将是你的骨架实现中的抽象方法。接下来,在接口中为所有可以直接在原语之上实现的方法提供默认方法,但请记住,你可能不会为诸如 equals 和 hashCode 之类的对象方法提供默认方法。如果原语和默认方法覆盖了接口,那么就完成了,不需要一个骨架实现类。否则,编写一个声明为实现接口的类,并实现所有剩余的接口方法。该类可能包含任何适合于任务的非公共字段和方法。 As a simple example, consider the Map.Entry interface. The obvious primitives are getKey, getValue, and (optionally) setValue. The interface specifies the behavior of equals and hashCode, and there is an obvious implementation of toString in terms of the primitives. Since you are not allowed to provide default implementations for the Object methods, all implementations are placed in the skeletal implementation class: -作为一个简单的例子,考虑一下 Map.Entry 接口。最明显的基本(方法)是 getKey、getValue 和(可选的)setValue。该接口指定了 equals 和 hashCode 的行为,并且在基本(方法)方面有 toString 的明显实现。由于不允许为对象方法提供默认实现,所有实现都放在骨架实现类中: +作为一个简单的例子,考虑一下 `Map.Entry` 接口。最明显的基本方法是 getKey、getValue 和(可选的)setValue。该接口指定了 equals 和 hashCode 的行为,并且在基本方法方面有 toString 的明显实现。由于不允许为对象方法提供默认实现,所有实现都放在骨架实现类中: ``` // Skeletal implementation class public abstract class AbstractMapEntry implements Map.Entry { - // Entries in a modifiable map must override this method - @Override public V setValue(V value) { - throw new UnsupportedOperationException(); - } - - // Implements the general contract of Map.Entry.equals - @Override public boolean equals(Object o) { - if (o == this) - return true; - if (!(o instanceof Map.Entry)) - return false; - Map.Entry e = (Map.Entry) o; - return Objects.equals(e.getKey(), getKey()) && Objects.equals(e.getValue(), getValue()); - } - - // Implements the general contract of Map.Entry.hashCode - @Override public int hashCode() { - return Objects.hashCode(getKey())^ Objects.hashCode(getValue()); - } - - @Override public String toString() { - return getKey() + "=" + getValue(); - } + // Entries in a modifiable map must override this method + @Override public V setValue(V value) { + throw new UnsupportedOperationException(); + } + + // Implements the general contract of Map.Entry.equals + @Override public boolean equals(Object o) { + if (o == this) + return true; + if (!(o instanceof Map.Entry)) + return false; + Map.Entry e = (Map.Entry) o; + return Objects.equals(e.getKey(), getKey()) && Objects.equals(e.getValue(), getValue()); + } + + // Implements the general contract of Map.Entry.hashCode + @Override public int hashCode() { + return Objects.hashCode(getKey())^ Objects.hashCode(getValue()); + } + + @Override public String toString() { + return getKey() + "=" + getValue(); + } } ``` Note that this skeletal implementation could not be implemented in the Map.Entry interface or as a subinterface because default methods are not permitted to override Object methods such as equals, hashCode, and toString. -注意,这个骨架实现不能在 Map.Entry 接口或子接口中实现,因为不允许默认方法覆盖诸如 equals、hashCode 和 toString 等对象方法。 +注意,这个骨架实现不能在 `Map.Entry` 接口或子接口中实现,因为不允许默认方法覆盖诸如 equals、hashCode 和 toString 等对象方法。 Because skeletal implementations are designed for inheritance, you should follow all of the design and documentation guidelines in Item 19. For brevity’s sake, the documentation comments were omitted from the previous example, but good documentation is absolutely essential in a skeletal implementation, whether it consists of default methods on an interface or a separate abstract class. -因为骨架实现是为继承而设计的,所以你应该遵循[Item-19](https://github.com/clxering/Effective-Java-3rd-edition-Chinese-English-bilingual/blob/master/Chapter-4/Chapter-4-Item-19-Design-and-document-for-inheritance-or-else-prohibit-it.md)中的所有设计和文档指南。为了简洁起见,在前面的示例中省略了文档注释,但是优秀的文档对于骨架实现来说是绝对必要的,不管它是由接口上的默认方法还是单独的抽象类组成。 +因为骨架实现是为继承而设计的,所以你应该遵循 [Item-19](https://github.com/clxering/Effective-Java-3rd-edition-Chinese-English-bilingual/blob/master/Chapter-4/Chapter-4-Item-19-Design-and-document-for-inheritance-or-else-prohibit-it.md) 中的所有设计和文档指南。为了简洁起见,在前面的示例中省略了文档注释,但是优秀的文档对于骨架实现来说是绝对必要的,不管它是由接口上的默认方法还是单独的抽象类组成。 A minor variant on the skeletal implementation is the simple implementation, exemplified by AbstractMap.SimpleEntry. A simple implementation is like a skeletal implementation in that it implements an interface and is designed for inheritance, but it differs in that it isn’t abstract: it is the simplest possible working implementation. You can use it as it stands or subclass it as circumstances warrant. -骨架实现的一个小变体是简单实现,例如 AbstractMap.SimpleEntry。一个简单的实现就像一个骨架实现,因为它实现了一个接口,并且是为继承而设计的,但是它的不同之处在于它不是抽象的:它是最简单的工作实现。你可以根据它的状态使用它,也可以根据情况对它进行子类化。 +骨架实现的一个小变体是简单实现,例如 `AbstractMap.SimpleEntry`。一个简单的实现就像一个骨架实现,因为它实现了一个接口,并且是为继承而设计的,但是它的不同之处在于它不是抽象的:它是最简单的工作实现。你可以根据它的状态使用它,也可以根据情况对它进行子类化。 To summarize, an interface is generally the best way to define a type that permits multiple implementations. If you export a nontrivial(adj. 非平凡的) interface, you should strongly consider providing a skeletal implementation to go with it. To the extent possible, you should provide the skeletal implementation via default methods on the interface so that all implementors of the interface can make use of it. That said, restrictions on interfaces typically mandate that a skeletal implementation take the form of an abstract class. diff --git a/Chapter-4/Chapter-4-Item-21-Design-interfaces-for-posterity.md b/Chapter-4/Chapter-4-Item-21-Design-interfaces-for-posterity.md index 6b3801b..5efdc43 100644 --- a/Chapter-4/Chapter-4-Item-21-Design-interfaces-for-posterity.md +++ b/Chapter-4/Chapter-4-Item-21-Design-interfaces-for-posterity.md @@ -4,13 +4,13 @@ Prior to Java 8, it was impossible to add methods to interfaces without breaking existing implementations. If you added a new method to an interface, existing implementations would, in general, lack the method, resulting in a compile-time error. In Java 8, the default method construct was added [JLS 9.4], with the intent of allowing the addition of methods to existing interfaces. But adding new methods to existing interfaces is fraught with risk. -在 Java 8 之前,在不破坏现有实现的情况下向接口添加方法是不可能的。如果在接口中添加新方法,现有的实现通常会缺少该方法,从而导致编译时错误。在 Java 8 中,添加了默认的方法构造[JLS 9.4],目的是允许向现有接口添加方法。但是向现有接口添加新方法充满了风险。 +在 Java 8 之前,在不破坏现有实现的情况下向接口添加方法是不可能的。如果在接口中添加新方法,现有的实现通常会缺少该方法,从而导致编译时错误。在 Java 8 中,添加了默认的方法构造 [JLS 9.4],目的是允许向现有接口添加方法。但是向现有接口添加新方法充满了风险。 The declaration for a default method includes a default implementation that is used by all classes that implement the interface but do not implement the default method. While the addition of default methods to Java makes it possible to add methods to an existing interface, there is no guarantee that these methods will work in all preexisting implementations. Default methods are “injected” into existing implementations without the knowledge or consent of their implementors. Before Java 8, these implementations were written with the tacit understanding that their interfaces would never acquire any new methods. 默认方法的声明包括一个默认实现,所有实现接口但不实现默认方法的类都使用这个默认实现。虽然向 Java 添加默认方法使向现有接口添加方法成为可能,但不能保证这些方法在所有现有实现中都能工作。默认方法被「注入」到现有的实现中,而无需实现者的知情或同意。在 Java 8 之前,编写这些实现时都默认它们的接口永远不会获得任何新方法。 -Many new default methods were added to the core collection interfaces in Java 8, primarily to facilitate the use of lambdas (Chapter 6). The Java libraries’ default methods are high-quality general-purpose implementations, and in most cases, they work fine. **But it is not always possible to write a default method that maintains(vt. 维持;继续;维修;主张;供养) all invariants(n. [数] 不变量,invariant 的复数;不变性) of every conceivable(adj. 可能的;想得到的,可想像的) implementation.** +Many new default methods were added to the core collection interfaces in Java 8, primarily to facilitate the use of lambdas (Chapter 6). The Java libraries’ default methods are high-quality general-purpose implementations, and in most cases, they work fine. **But it is not always possible to write a default method that maintains all invariants of every conceivable implementation.** Java 8 的核心集合接口增加了许多新的默认方法,主要是为了方便 lambdas 的使用(第 6 章)。**但是,并不总是能够编写一个默认方法来维护每个可想到的实现的所有不变量** @@ -35,7 +35,7 @@ default boolean removeif(predicate filter) { This is the best general-purpose implementation one could possibly write for the removeIf method, but sadly, it fails on some real-world Collection implementations. For example, consider org.apache.commons.collections4.collection.SynchronizedCollection. This class, from the Apache Commons library, is similar to the one returned by the static factory Collections.-synchronizedCollection in java.util. The Apache version additionally provides the ability to use a client-supplied object for locking, in place of the collection. In other words, it is a wrapper class (Item 18), all of whose methods synchronize on a locking object before delegating to the wrapped collection. -这是为 removeIf 方法编写的最好的通用实现,但遗憾的是,它在一些实际的集合实现中失败了。例如,考虑 org.apache.commons.collections4.collection.SynchronizedCollection。这个类来自 Apache Commons 库,类似于静态工厂集合返回的类。-synchronizedCollection java.util。Apache 版本还提供了使用客户机提供的对象进行锁定的功能,以代替集合。换句话说,它是一个包装器类([Item-18](https://github.com/clxering/Effective-Java-3rd-edition-Chinese-English-bilingual/blob/master/Chapter-4/Chapter-4-Item-18-Favor-composition-over-inheritance.md)),其所有方法在委托给包装集合之前同步锁定对象。 +这是为 removeIf 方法编写的最好的通用实现,但遗憾的是,它在一些实际的集合实现中失败了。例如,考虑 `org.apache.commons.collections4.collection.SynchronizedCollection`。这个类来自 Apache Commons 库,类似于静态工厂集合返回的类。-synchronizedCollection java.util。Apache 版本还提供了使用客户端提供的对象进行锁定的功能,以代替集合。换句话说,它是一个包装器类([Item-18](https://github.com/clxering/Effective-Java-3rd-edition-Chinese-English-bilingual/blob/master/Chapter-4/Chapter-4-Item-18-Favor-composition-over-inheritance.md)),其所有方法在委托给包装集合之前同步锁定对象。 The Apache SynchronizedCollection class is still being actively maintained, but as of this writing, it does not override the removeIf method. If this class is used in conjunction with Java 8, it will therefore inherit the default implementation of removeIf, which does not, indeed cannot, maintain the class’s fundamental promise: to automatically synchronize around each method invocation. The default implementation knows nothing about synchronization and has no access to the field that contains the locking object. If a client calls the removeIf method on a SynchronizedCollection instance in the presence of concurrent modification of the collection by another thread, a ConcurrentModificationException or other unspecified behavior may result. @@ -43,7 +43,7 @@ Apache SynchronizedCollection 类仍然得到了积极的维护,但是在编 In order to prevent this from happening in similar Java platform libraries implementations, such as the package-private class returned by Collections.synchronizedCollection, the JDK maintainers had to override the default removeIf implementation and other methods like it to perform the necessary synchronization before invoking the default implementation. Preexisting collection implementations that were not part of the Java platform did not have the opportunity to make analogous changes in lockstep with the interface change, and some have yet to do so. -为了防止类似的 Java 平台库实现中发生这种情况,例如集合返回的包私有类。synchronizedCollection, JDK 维护人员必须覆盖默认的 removeIf 实现和其他类似的方法,以便在调用默认实现之前执行必要的同步。不属于 Java 平台的现有集合实现没有机会与接口更改同步进行类似的更改,有些还没有这样做。 +为了防止类似的 Java 库实现(例如 `Collections.synchronizedCollection` 返回的包私有类)中发生这种情况,JDK 维护人员必须覆盖默认的 removeIf 实现和其他类似的方法,以便在调用默认实现之前执行必要的同步。不属于 Java 平台的现有集合实现没有机会与接口更改同步进行类似的更改,有些实现还没有这样做。 **In the presence of default methods, existing implementations of an interface may compile without error or warning but fail at runtime.** While not terribly common, this problem is not an isolated incident either. A handful of the methods added to the collections interfaces in Java 8 are known to be susceptible, and a handful of existing implementations are known to be affected. @@ -63,4 +63,4 @@ The moral is clear. Even though default methods are now a part of the Java platf Therefore, it is critically important to test each new interface before you release it. Multiple programmers should implement each interface in different ways. At a minimum, you should aim for three diverse implementations. Equally important is to write multiple client programs that use instances of each new interface to perform various tasks. This will go a long way toward ensuring that each interface satisfies all of its intended uses. These steps will allow you to discover flaws in interfaces before they are released, when you can still correct them easily. **While it may be possible to correct some interface flaws after an interface is released, you cannot count on it.** -因此,在发布每个新接口之前对其进行测试非常重要。多个程序员应该以不同的方式实现每个接口。至少,你应该以三种不同的实现为目标。同样重要的是编写多个客户端程序,这些程序使用每个新接口的实例来执行各种任务。这将大大有助于确保每个接口满足其所有预期用途。这些步骤将允许你在接口被发布之前发现它们的缺陷,而你仍然可以轻松地纠正它们。**虽然在接口被发布之后可以纠正一些接口缺陷,但是你不能指望(这种方式)。** +因此,在发布每个新接口之前对其进行测试非常重要。多个程序员应该以不同的方式实现每个接口。至少,你应该以三种不同的实现为目标。同样重要的是编写多个客户端程序,这些程序使用每个新接口的实例来执行各种任务。这将大大有助于确保每个接口满足其所有预期用途。这些步骤将允许你在接口被发布之前发现它们的缺陷,而你仍然可以轻松地纠正它们。**虽然在接口被发布之后可以纠正一些接口缺陷,但是你不能指望这种方式。** diff --git a/Chapter-4/Chapter-4-Item-22-Use-interfaces-only-to-define-types.md b/Chapter-4/Chapter-4-Item-22-Use-interfaces-only-to-define-types.md index 800a6ee..49e8d23 100644 --- a/Chapter-4/Chapter-4-Item-22-Use-interfaces-only-to-define-types.md +++ b/Chapter-4/Chapter-4-Item-22-Use-interfaces-only-to-define-types.md @@ -13,24 +13,24 @@ One kind of interface that fails this test is the so-called constant interface. ``` // Constant interface antipattern - do not use! public interface PhysicalConstants { - // Avogadro's number (1/mol) - static final double AVOGADROS_NUMBER = 6.022_140_857e23; + // Avogadro's number (1/mol) + static final double AVOGADROS_NUMBER = 6.022_140_857e23; - // Boltzmann constant (J/K) - static final double BOLTZMANN_CONSTANT = 1.380_648_52e-23; + // Boltzmann constant (J/K) + static final double BOLTZMANN_CONSTANT = 1.380_648_52e-23; - // Mass of the electron (kg) - static final double ELECTRON_MASS = 9.109_383_56e-31; + // Mass of the electron (kg) + static final double ELECTRON_MASS = 9.109_383_56e-31; } ``` The constant interface pattern is a poor use of interfaces. That a class uses some constants internally is an implementation detail. Implementing a constant interface causes this implementation detail to leak into the class’s exported API. It is of no consequence to the users of a class that the class implements a constant interface. In fact, it may even confuse them. Worse, it represents a commitment: if in a future release the class is modified so that it no longer needs to use the constants, it still must implement the interface to ensure binary compatibility. If a nonfinal class implements a constant interface, all of its subclasses will have their namespaces polluted by the constants in the interface. -常量接口模式是对接口的糟糕使用。类内部使用一些常量是实现细节。实现常量接口会导致这个实现细节泄漏到类的导出 API 中。对于类的用户来说,类实现一个常量接口并(没有什么价值)。事实上,这甚至会让他们感到困惑。更糟糕的是,它代表了一种承诺:如果在将来的版本中修改了类,使其不再需要使用常量,那么它仍然必须实现接口以确保二进制兼容性。如果一个非 final 类实现了一个常量接口,那么它的所有子类的名称空间都会被接口中的常量所污染。 +常量接口模式是对接口的糟糕使用。类内部使用一些常量是实现细节。实现常量接口会导致这个实现细节泄漏到类的导出 API 中。对于类的用户来说,类实现一个常量接口并没有什么价值。事实上,这甚至会让他们感到困惑。更糟糕的是,它代表了一种承诺:如果在将来的版本中修改了类,使其不再需要使用常量,那么它仍然必须实现接口以确保二进制兼容性。如果一个非 final 类实现了一个常量接口,那么它的所有子类的名称空间都会被接口中的常量所污染。 There are several constant interfaces in the Java platform libraries, such as java.io.ObjectStreamConstants. These interfaces should be regarded as anomalies and should not be emulated. -Java 平台库中有几个常量接口,例如 java.io.ObjectStreamConstants。这些接口应该被视为(反例),不应该被(效仿)。 +Java 库中有几个常量接口,例如 `java.io.ObjectStreamConstants`。这些接口应该被视为反例,不应该被效仿。 If you want to export constants, there are several reasonable choices. If the constants are strongly tied to an existing class or interface, you should add them to the class or interface. For example, all of the boxed numerical primitive classes, such as Integer and Double, export MIN_VALUE and MAX_VALUE constants. If the constants are best viewed as members of an enumerated type, you should export them with an enum type (Item 34). Otherwise, you should export the constants with a noninstantiable utility class (Item 4). Here is a utility class version of the PhysicalConstants example shown earlier: @@ -41,30 +41,30 @@ If you want to export constants, there are several reasonable choices. If the co package com.effectivejava.science; public class PhysicalConstants { - private PhysicalConstants() { } // Prevents instantiation(将构造私有,阻止实例化) - public static final double AVOGADROS_NUMBER = 6.022_140_857e23; - public static final double BOLTZMANN_CONST = 1.380_648_52e-23; - public static final double ELECTRON_MASS = 9.109_383_56e-31; + private PhysicalConstants() { } // Prevents instantiation(将构造私有,阻止实例化) + public static final double AVOGADROS_NUMBER = 6.022_140_857e23; + public static final double BOLTZMANN_CONST = 1.380_648_52e-23; + public static final double ELECTRON_MASS = 9.109_383_56e-31; } ``` Incidentally, note the use of the underscore character ( _ ) in the numeric literals. Underscores, which have been legal since Java 7, have no effect on the values of numeric literals, but can make them much easier to read if used with discretion. Consider adding underscores to numeric literals, whether fixed of floating point, if they contain five or more consecutive digits. For base ten literals, whether integral or floating point, you should use underscores to separate literals into groups of three digits indicating positive and negative powers of one thousand. -顺便说一下,请注意在数字字面值中使用了下划线( _ )。下划线自 Java 7 以来一直是合法的,它对数字字面值没有影响,如果谨慎使用,可以使它们更容易阅读。考虑添加下划线到数字字面值,无论是固定的浮点数,如果它们包含五个或多个连续数字。对于以 10 为基数的字面值,无论是整数还是浮点数,都应该使用下划线将字面值分隔为三位数,表示一千的正幂和负幂。 +顺便说一下,请注意在数字字面值中使用了下划线( _ )。下划线自 Java 7 以来一直是合法的,它对数字字面值没有影响,如果谨慎使用,可以使它们更容易阅读。考虑添加下划线到数字字面值,无论是固定的浮点数,如果它们包含五个或多个连续数字。对于以 10 为基数的字面值,无论是整数还是浮点数,都应该使用下划线将字面值分隔为三位数,表示 1000 的正幂和负幂。 Normally a utility class requires clients to qualify constant names with a class name, for example, PhysicalConstants.AVOGADROS_NUMBER. If you make heavy use of the constants exported by a utility class, you can avoid the need for qualifying the constants with the class name by making use of the static import facility: -通常,工具类要求客户端使用类名来限定常量名,例如 PhysicalConstants.AVOGADROS_NUMBER。如果你大量使用工具类导出的常量,你可以通过使用静态导入机制来避免使用类名限定常量: +通常,工具类要求客户端使用类名来限定常量名,例如 `PhysicalConstants.AVOGADROS_NUMBER`。如果你大量使用工具类导出的常量,你可以通过使用静态导入机制来避免使用类名限定常量: ``` // Use of static import to avoid qualifying constants import static com.effectivejava.science.PhysicalConstants.*; public class Test { - double atoms(double mols) { - return AVOGADROS_NUMBER * mols; - } ... - // Many more uses of PhysicalConstants justify static import + double atoms(double mols) { + return AVOGADROS_NUMBER * mols; + } ... + // Many more uses of PhysicalConstants justify static import } ``` diff --git a/Chapter-4/Chapter-4-Item-23-Prefer-class-hierarchies-to-tagged-classes.md b/Chapter-4/Chapter-4-Item-23-Prefer-class-hierarchies-to-tagged-classes.md index 4f7639b..b9a9ace 100644 --- a/Chapter-4/Chapter-4-Item-23-Prefer-class-hierarchies-to-tagged-classes.md +++ b/Chapter-4/Chapter-4-Item-23-Prefer-class-hierarchies-to-tagged-classes.md @@ -10,11 +10,15 @@ Occasionally you may run across a class whose instances come in two or more flav // Tagged class - vastly inferior to a class hierarchy! class Figure { enum Shape {RECTANGLE, CIRCLE}; + // Tag field - the shape of this figure final Shape shape; + // These fields are used only if shape is RECTANGLE double length; + double width; + // This field is used only if shape is CIRCLE double radius; @@ -117,4 +121,4 @@ Note that the fields in the above hierarchy are accessed directly rather than by In summary, tagged classes are seldom appropriate. If you’re tempted to write a class with an explicit tag field, think about whether the tag could be eliminated and the class replaced by a hierarchy. When you encounter an existing class with a tag field, consider refactoring it into a hierarchy. -总之,标签类很少(有)合适(的时候)。如果你想用显式标签字段编写类,请考虑是否可以消除标签并用层次结构替换类。当你遇到带有标签字段的现有类时,请考虑将其重构为层次结构。 +总之,标签类很少有合适的时候。如果你想用显式标签字段编写类,请考虑是否可以消除标签并用层次结构替换类。当你遇到带有标签字段的现有类时,请考虑将其重构为层次结构。 diff --git a/Chapter-4/Chapter-4-Item-24-Favor-static-member-classes-over-nonstatic.md b/Chapter-4/Chapter-4-Item-24-Favor-static-member-classes-over-nonstatic.md index 92f50b0..599ff79 100644 --- a/Chapter-4/Chapter-4-Item-24-Favor-static-member-classes-over-nonstatic.md +++ b/Chapter-4/Chapter-4-Item-24-Favor-static-member-classes-over-nonstatic.md @@ -4,27 +4,27 @@ A nested class is a class defined within another class. A nested class should exist only to serve its enclosing class. If a nested class would be useful in some other context, then it should be a top-level class. There are four kinds of nested classes: static member classes, nonstatic member classes, anonymous classes, and local classes. All but the first kind are known as inner classes. This item tells you when to use which kind of nested class and why. -嵌套类是在另一个类中定义的类。嵌套类应该只为外部类服务。如果嵌套类在其他(环境)中有用,那么它应该是顶级类。有四种嵌套类:静态成员类、非静态成员类、匿名类和局部类。除了第一种,所有的类都被称为内部类。本条目会告诉你什么时候使用哪种嵌套类以及原因。 +嵌套类是在另一个类中定义的类。嵌套类应该只为外部类服务。如果嵌套类在其他环境中有用,那么它应该是顶级类。有四种嵌套类:静态成员类、非静态成员类、匿名类和局部类。除了第一种,所有的类都被称为内部类。本条目会告诉你什么时候使用哪种嵌套类以及原因。 A static member class is the simplest kind of nested class. It is best thought of as an ordinary class that happens to be declared inside another class and has access to all of the enclosing class’s members, even those declared private. A static member class is a static member of its enclosing class and obeys the same accessibility rules as other static members. If it is declared private, it is accessible only within the enclosing class, and so forth. -静态成员类是最简单的嵌套类。最好把它看做是一个普通的类,(只是)碰巧在另一个类中声明而已,并且可以访问外部类的所有成员,甚至那些声明为 private 的成员。静态成员类是其外部类的静态成员,并且遵守与其他静态成员相同的可访问性规则。如果声明为私有,则只能在外部类中访问,等等。 +静态成员类是最简单的嵌套类。最好把它看做是一个普通的类,只是碰巧在另一个类中声明而已,并且可以访问外部类的所有成员,甚至那些声明为 private 的成员。静态成员类是其外部类的静态成员,并且遵守与其他静态成员相同的可访问性规则。如果声明为私有,则只能在外部类中访问,等等。 One common use of a static member class is as a public helper class, useful only in conjunction(n. 结合;[语] 连接词;同时发生) with its outer class. For example, consider an enum describing the operations supported by a calculator (Item 34). The Operation enum should be a public static member class of the Calculator class. Clients of Calculator could then refer to operations using names like Calculator.Operation.PLUS and Calculator.Operation.MINUS. -静态成员类的一个常见用法是作为公有的辅助类,只有与它的外部类一起使用时才有意义。例如,考虑一个描述了计算器支持的各种操作的枚举([Item-34](https://github.com/clxering/Effective-Java-3rd-edition-Chinese-English-bilingual/blob/master/Chapter-6/Chapter-6-Item-34-Use-enums-instead-of-int-constants.md))。Operation 枚举应该是 Calculator 类的公有静态成员类,Calculator 类的客户端就可以用 Calculator.Operation.PLUS 和 Calculator.Operation.MINUS 等名称来引用这些操作。 +静态成员类的一个常见用法是作为公有的辅助类,只有与它的外部类一起使用时才有意义。例如,考虑一个描述了计算器支持的各种操作的枚举([Item-34](https://github.com/clxering/Effective-Java-3rd-edition-Chinese-English-bilingual/blob/master/Chapter-6/Chapter-6-Item-34-Use-enums-instead-of-int-constants.md))。Operation 枚举应该是 Calculator 类的公有静态成员类,Calculator 类的客户端就可以用 `Calculator.Operation.PLUS` 和 `Calculator.Operation.MINUS` 等名称来引用这些操作。 Syntactically(adv. 依照句法地;在语句构成上), the only difference between static and nonstatic member classes is that static member classes have the modifier static in their declarations. Despite(n. 轻视;憎恨;侮辱;prep. 尽管,不管) the syntactic similarity, these two kinds of nested classes are very different. Each instance of a nonstatic member class is implicitly associated with an enclosing instance of its containing class. Within instance methods of a nonstatic member class, you can invoke methods on the enclosing instance or obtain a reference to the enclosing instance using the qualified this construct [JLS, 15.8.4]. If an instance of a nested class can exist in isolation from an instance of its enclosing class, then the nested class must be a static member class: it is impossible to create an instance of a nonstatic member class without an enclosing instance. -从语法上讲,静态成员类和非静态成员类之间的唯一区别是静态成员类在其声明中具有修饰符 static。尽管语法相似,但这两种嵌套类有很大不同。非静态成员类的每个实例都隐式地与外部类的外部实例相关联。在非静态成员类的实例方法中,你可以调用外部实例上的方法,或者使用受限制的 this 构造获得对外部实例的引用[JLS, 15.8.4]。如果嵌套类的实例可以独立于外部类的实例存在,那么嵌套类必须是静态成员类:如果没有外部实例,就不可能创建非静态成员类的实例。 +从语法上讲,静态成员类和非静态成员类之间的唯一区别是静态成员类在其声明中具有修饰符 static。尽管语法相似,但这两种嵌套类有很大不同。非静态成员类的每个实例都隐式地与外部类的外部实例相关联。在非静态成员类的实例方法中,你可以调用外部实例上的方法,或者使用受限制的 this 构造获得对外部实例的引用 [JLS, 15.8.4]。如果嵌套类的实例可以独立于外部类的实例存在,那么嵌套类必须是静态成员类:如果没有外部实例,就不可能创建非静态成员类的实例。 The association between a nonstatic member class instance and its enclosing instance is established when the member class instance is created and cannot be modified thereafter. Normally, the association is established automatically by invoking a nonstatic member class constructor from within an instance method of the enclosing class. It is possible, though rare, to establish the association manually using the expression enclosingInstance.new MemberClass(args). As you would expect, the association takes up space in the nonstatic member class instance and adds time to its construction. -非静态成员类实例与外部实例之间的关联是在创建成员类实例时建立的,之后无法修改。通常,关联是通过从外部类的实例方法中调用非静态成员类构造函数自动建立的。使用 enclosingInstance.new MemberClass(args)表达式手动建立关联是可能的,尽管这种情况很少见。正如你所期望的那样,关联占用了非静态成员类实例中的空间,并为其构造增加了时间。 +非静态成员类实例与外部实例之间的关联是在创建成员类实例时建立的,之后无法修改。通常,关联是通过从外部类的实例方法中调用非静态成员类构造函数自动建立的。使用 `enclosingInstance.new MemberClass(args)` 表达式手动建立关联是可能的,尽管这种情况很少见。正如你所期望的那样,关联占用了非静态成员类实例中的空间,并为其构造增加了时间。 One common use of a nonstatic member class is to define an Adapter [Gamma95] that allows an instance of the outer class to be viewed as an instance of some unrelated class. For example, implementations of the Map interface typically use nonstatic member classes to implement their collection views, which are returned by Map’s keySet, entrySet, and values methods. Similarly, implementations of the collection interfaces, such as Set and List, typically use nonstatic member classes to implement their iterators: -非静态成员类的一个常见用法是定义一个适配器[Gamma95],它允许外部类的实例被视为某个不相关类的实例。例如,Map 接口的实现通常使用非静态成员类来实现它们的集合视图,这些视图由 Map 的 keySet、entrySet 和 values 方法返回。类似地,集合接口的实现,例如 Set 和 List,通常使用非静态成员类来实现它们的迭代器: +非静态成员类的一个常见用法是定义一个适配器 [Gamma95],它允许外部类的实例被视为某个不相关类的实例。例如,Map 接口的实现通常使用非静态成员类来实现它们的集合视图,这些视图由 Map 的 keySet、entrySet 和 values 方法返回。类似地,集合接口的实现,例如 Set 和 List,通常使用非静态成员类来实现它们的迭代器: ``` // Typical use of a nonstatic member class @@ -42,7 +42,7 @@ public class MySet extends AbstractSet { **If you declare a member class that does not require access to an enclosing instance, always put the static modifier in its declaration,** making it a static rather than a nonstatic member class. If you omit this modifier, each instance will have a hidden extraneous reference to its enclosing instance. As previously mentioned, storing this reference takes time and space. More seriously, it can result in the enclosing instance being retained when it would otherwise be eligible for garbage collection (Item 7). The resulting memory leak can be catastrophic. It is often difficult to detect because the reference is invisible. -**如果声明的成员类不需要访问外部的实例,那么(应)始终在声明中添加 static 修饰符,使其成为静态的而不是非静态的成员类。** 如果省略这个修饰符,每个实例都有一个隐藏的对其外部实例的额外引用。如前所述,存储此引用需要时间和空间。更严重的是,它可能会在(满足)进行垃圾收集(条件)时(仍)保留外部类的实例([Item-7](https://github.com/clxering/Effective-Java-3rd-edition-Chinese-English-bilingual/blob/master/Chapter-2/Chapter-2-Item-7-Eliminate-obsolete-object-references.md))。由于引用是不可见的,因此通常很难检测到。 +**如果声明的成员类不需要访问外部的实例,那么应始终在声明中添加 static 修饰符,使其成为静态的而不是非静态的成员类。** 如果省略这个修饰符,每个实例都有一个隐藏的对其外部实例的额外引用。如前所述,存储此引用需要时间和空间。更严重的是,它可能会在满足进行垃圾收集条件时仍保留外部类的实例([Item-7](https://github.com/clxering/Effective-Java-3rd-edition-Chinese-English-bilingual/blob/master/Chapter-2/Chapter-2-Item-7-Eliminate-obsolete-object-references.md))。由于引用是不可见的,因此通常很难检测到。 A common use of private static member classes is to represent components of the object represented by their enclosing class. For example, consider a Map instance, which associates keys with values. Many Map implementations have an internal Entry object for each key-value pair in the map. While each entry is associated with a map, the methods on an entry (getKey, getValue, and setValue) do not need access to the map. Therefore, it would be wasteful to use a nonstatic member class to represent entries: a private static member class is best. If you accidentally omit the static modifier in the entry declaration, the map will still work, but each entry will contain a superfluous reference to the map, which wastes space and time. @@ -54,7 +54,7 @@ It is doubly important to choose correctly between a static and a nonstatic memb As you would expect, an anonymous class has no name. It is not a member of its enclosing class. Rather than being declared along with other members, it is simultaneously declared and instantiated at the point of use. Anonymous classes are permitted at any point in the code where an expression is legal. Anonymous classes have enclosing instances if and only if they occur in a nonstatic context. But even if they occur in a static context, they cannot have any static members other than constant variables, which are final primitive or string fields initialized to constant expressions [JLS, 4.12.4]. -如你所料,匿名类没有名称。它不是外部类的成员。它不是与其他成员一起声明的,而是在使用时同时声明和实例化。匿名类在代码中任何一个表达式合法的地方都是被允许的。当且仅当它们发生在非静态(环境)中时,匿名类(才)具有外部类实例。但是,即使它们发生在静态(环境)中,它们也不能有除常量变量以外的任何静态成员,常量变量是最终的基本类型或初始化为常量表达式的字符串字段[JLS, 4.12.4]。 +如你所料,匿名类没有名称。它不是外部类的成员。它不是与其他成员一起声明的,而是在使用时同时声明和实例化。匿名类在代码中任何一个表达式合法的地方都是被允许的。当且仅当它们发生在非静态环境中时,匿名类才具有外部类实例。但是,即使它们发生在静态环境中,它们也不能有除常量变量以外的任何静态成员,常量变量是最终的基本类型或初始化为常量表达式的字符串字段 [JLS, 4.12.4]。 There are many limitations on the applicability of anonymous classes. You can’t instantiate them except at the point they’re declared. You can’t perform instanceof tests or do anything else that requires you to name the class. You can’t declare an anonymous class to implement multiple interfaces or to extend a class and implement an interface at the same time. Clients of an anonymous class can’t invoke any members except those it inherits from its supertype. Because anonymous classes occur in the midst of expressions, they must be kept short—about ten lines or fewer—or readability will suffer. @@ -62,12 +62,12 @@ There are many limitations on the applicability of anonymous classes. You can’ Before lambdas were added to Java (Chapter 6), anonymous classes were the preferred means of creating small function objects and process objects on the fly, but lambdas are now preferred (Item 42). Another common use of anonymous classes is in the implementation of static factory methods (see intArrayAsList in Item 20). -在 lambdas 被添加到 Java(第 6 章)之前,匿名类是动态创建小函数对象和进程对象的首选方法,但 lambdas 现在是首选方法([Item-42](https://github.com/clxering/Effective-Java-3rd-edition-Chinese-English-bilingual/blob/master/Chapter-7/Chapter-7-Item-42-Prefer-lambdas-to-anonymous-classes.md))。匿名类的另一个常见用法是实现静态工厂方法(参见[Item-20](https://github.com/clxering/Effective-Java-3rd-edition-Chinese-English-bilingual/blob/master/Chapter-4/Chapter-4-Item-20-Prefer-interfaces-to-abstract-classes.md)中的 intArrayAsList)。 +在 lambdas 被添加到 Java(Chapter 6)之前,匿名类是动态创建小函数对象和进程对象的首选方法,但 lambdas 现在是首选方法([Item-42](https://github.com/clxering/Effective-Java-3rd-edition-Chinese-English-bilingual/blob/master/Chapter-7/Chapter-7-Item-42-Prefer-lambdas-to-anonymous-classes.md))。匿名类的另一个常见用法是实现静态工厂方法(参见 [Item-20](https://github.com/clxering/Effective-Java-3rd-edition-Chinese-English-bilingual/blob/master/Chapter-4/Chapter-4-Item-20-Prefer-interfaces-to-abstract-classes.md) 中的 intArrayAsList 类)。 Local classes are the least frequently used of the four kinds of nested classes. A local class can be declared practically anywhere a local variable can be declared and obeys the same scoping rules. Local classes have attributes in common with each of the other kinds of nested classes. Like member classes, they have names and can be used repeatedly. Like anonymous classes, they have enclosing instances only if they are defined in a nonstatic context, and they cannot contain static members. And like anonymous classes, they should be kept short so as not to harm readability. -局部类是四种嵌套类中最不常用的。局部类几乎可以在任何(能够)声明局部变量的地方(使用),并且遵守相同的作用域规则。局部类具有与其他嵌套类相同的属性。与成员类一样,它们有名称,可以重复使用。与匿名类一样,它们只有在非静态(环境)中定义的情况下才具有外部类实例,而且它们不能包含静态成员。和匿名类一样,它们应该保持简短,以免损害可读性。 +局部类是四种嵌套类中最不常用的。局部类几乎可以在任何能够声明局部变量的地方使用,并且遵守相同的作用域规则。局部类具有与其他嵌套类相同的属性。与成员类一样,它们有名称,可以重复使用。与匿名类一样,它们只有在非静态环境中定义的情况下才具有外部类实例,而且它们不能包含静态成员。和匿名类一样,它们应该保持简短,以免损害可读性。 To recap, there are four different kinds of nested classes, and each has its place. If a nested class needs to be visible outside of a single method or is too long to fit comfortably inside a method, use a member class. If each instance of a member class needs a reference to its enclosing instance, make it nonstatic; otherwise, make it static. Assuming the class belongs inside a method, if you need to create instances from only one location and there is a preexisting type that characterizes the class, make it an anonymous class; otherwise, make it a local class. -简单回顾一下,有四种不同类型的嵌套类,每一种都有自己的用途。如果嵌套的类需要在单个方法之外可见,或者太长,(不适合)放入方法中,则使用成员类。如果成员类的每个实例都需要引用其外部类实例,则使其非静态;否则,让它保持静态。假设(嵌套)类属于方法内部,如果你只需要从一个位置创建实例,并且存在一个(能够)描述类的现有类型,那么将其设置为匿名类;否则,将其设置为局部类。 +简单回顾一下,有四种不同类型的嵌套类,每一种都有自己的用途。如果嵌套的类需要在单个方法之外可见,或者太长,不适合放入方法中,则使用成员类。如果成员类的每个实例都需要引用其外部类实例,则使其非静态;否则,让它保持静态。假设嵌套类属于方法内部,如果你只需要从一个位置创建实例,并且存在一个能够描述类的现有类型,那么将其设置为匿名类;否则,将其设置为局部类。 diff --git a/Chapter-4/Chapter-4-Item-25-Limit-source-files-to-a-single-top-level-class.md b/Chapter-4/Chapter-4-Item-25-Limit-source-files-to-a-single-top-level-class.md index 2bc8a3b..3165187 100644 --- a/Chapter-4/Chapter-4-Item-25-Limit-source-files-to-a-single-top-level-class.md +++ b/Chapter-4/Chapter-4-Item-25-Limit-source-files-to-a-single-top-level-class.md @@ -4,55 +4,55 @@ While the Java compiler lets you define multiple top-level classes in a single source file, there are no benefits associated with doing so, and there are significant risks. The risks stem from the fact that defining multiple top-level classes in a source file makes it possible to provide multiple definitions for a class. Which definition gets used is affected by the order in which the source files are passed to the compiler. To make this concrete, consider this source file, which contains only a Main class that refers to members of two other top-level classes (Utensil and Dessert): -虽然 Java 编译器允许你在单个源文件中定义多个顶层类,但这样做没有任何好处,而且存在重大风险。这种风险源于这样一个事实,即在源文件中定义多个顶层类使得为一个类提供多个定义成为可能。所使用的定义受源文件传给编译器的顺序的影响。要使(这个问题)具体化,请考虑这个源文件,它只包含一个主类,该主类引用另外两个顶层类的成员(餐具和甜点): +虽然 Java 编译器允许你在单个源文件中定义多个顶层类,但这样做没有任何好处,而且存在重大风险。这种风险源于这样一个事实,即在源文件中定义多个顶层类使得为一个类提供多个定义成为可能。所使用的定义受源文件传给编译器的顺序的影响。要使这个问题具体化,请考虑这个源文件,它只包含一个主类,该主类引用另外两个顶层类的成员(餐具和甜点): ``` public class Main { - public static void main(String[] args) { - System.out.println(Utensil.NAME + Dessert.NAME); - } + public static void main(String[] args) { + System.out.println(Utensil.NAME + Dessert.NAME); + } } ``` Now suppose you define both Utensil and Dessert in a single source file named Utensil.java: -现在假设你在一个名为 Utensil.java 的源文件中定义了餐具和甜点: +现在假设你在一个名为 `Utensil.java` 的源文件中定义了餐具和甜点: ``` // Two classes defined in one file. Don't ever do this! // 在一个文件中定义两个类。永远不要这样做! class Utensil { - static final String NAME = "pan"; + static final String NAME = "pan"; } class Dessert { - static final String NAME = "cake"; + static final String NAME = "cake"; } ``` Of course the main program prints pancake. Now suppose you accidentally make another source file named Dessert.java that defines the same two classes: -当然,main 方法应该输出 pancake。现在假设你意外地制作了另一个名为 Dessert 的源文件。java 定义了相同的两个类: +当然,main 方法应该输出 pancake。现在假设你意外地制作了另一个名为 Dessert 的源文件。java 定义了相同的两个类: ``` // Two classes defined in one file. Don't ever do this! // 在一个文件中定义两个类。永远不要这样做! class Utensil { - static final String NAME = "pot"; + static final String NAME = "pot"; } class Dessert { - static final String NAME = "pie"; + static final String NAME = "pie"; } ``` If you’re lucky enough to compile the program with the command javac Main.java Dessert.java, the compilation will fail, and the compiler will tell you that you’ve multiply defined the classes Utensil and Dessert. This is so because the compiler will first compile Main.java, and when it sees the reference to Utensil (which precedes the reference to Dessert), it will look in Utensil.java for this class and find both Utensil and Dessert. When the compiler encounters Dessert.java on the command line, it will pull in that file too, causing it to encounter both definitions of Utensil and Dessert. -如果你足够幸运,使用`javac Main.java Dessert.java`命令编译程序时,编译将失败,编译器将告诉你多重定义了餐具和甜点。这是因为编译器将首先编译 Main.java,当它看到对 Utensil 的引用(在对 Dessert 的引用之前)时,它将在 Utensil.java 中查找这个类,并找到餐具和甜点。当编译器在命令行上遇到 Dessert.java 时,(编译器)也会载入该文件,导致(编译器)同时遇到 Utensil 和 Dessert 的定义。 +如果你足够幸运,使用 `javac Main.java Dessert.java` 命令编译程序时,编译将失败,编译器将告诉你多重定义了餐具和甜点。这是因为编译器将首先编译 Main.java,当它看到对 Utensil 的引用(在对 Dessert 的引用之前)时,它将在 Utensil.java 中查找这个类,并找到餐具和甜点。当编译器在命令行上遇到 Dessert.java 时,(编译器)也会载入该文件,导致(编译器)同时遇到 Utensil 和 Dessert 的定义。 If you compile the program with the command javac Main.java or javac Main.java Utensil.java, it will behave as it did before you wrote the Dessert.java file, printing pancake. But if you compile the program with the command javac Dessert.java Main.java, it will print potpie. The behavior of the program is thus affected by the order in which the source files are passed to the compiler, which is clearly unacceptable. -如果你使用命令`javac Main.java`或`javac Main.java Utensil.java`编译程序,它的行为将与编写 Dessert.java 文件(打印 pancake)之前一样。但是如果你使用命令`javac Dessert.java Main.java`编译程序,它将打印 potpie。因此,程序的行为受到源文件传递给编译器的顺序的影响,这显然是不可接受的。 +如果你使用命令 `javac Main.java` 或 `javac Main.java Utensil.java` 编译程序,它的行为将与编写 `Dessert.java` 文件(打印 pancake)之前一样。但是如果你使用命令 `javac Dessert.java Main.java` 编译程序,它将打印 potpie。因此,程序的行为受到源文件传递给编译器的顺序的影响,这显然是不可接受的。 Fixing the problem is as simple as splitting the top-level classes (Utensil and Dessert, in the case of our example) into separate source files. If you are tempted to put multiple top-level classes into a single source file, consider using static member classes (Item 24) as an alternative to splitting the classes into separate source files. If the classes are subservient to another class, making them into static member classes is generally the better alternative because it enhances readability and makes it possible to reduce the accessibility of the classes by declaring them private (Item 15). Here is how our example looks with static member classes: @@ -62,20 +62,20 @@ Fixing the problem is as simple as splitting the top-level classes (Utensil and // Static member classes instead of multiple top-level classes public class Test { - public static void main(String[] args) { - System.out.println(Utensil.NAME + Dessert.NAME); - } + public static void main(String[] args) { + System.out.println(Utensil.NAME + Dessert.NAME); + } - private static class Utensil { - static final String NAME = "pan"; - } + private static class Utensil { + static final String NAME = "pan"; + } - private static class Dessert { - static final String NAME = "cake"; - } + private static class Dessert { + static final String NAME = "cake"; + } } ``` -The lesson is clear: Never put multiple top-level classes or interfaces in a single source file. Following this rule guarantees(n. 保证;v. 保证,guarantee 的第三人称单数) that you can’t have multiple definitions for a single class at compile time. This in turn guarantees that the class files generated by compilation, and the behavior of the resulting program, are independent of the order in which the source files are passed to the compiler. +The lesson is clear: Never put multiple top-level classes or interfaces in a single source file. Following this rule guarantees that you can’t have multiple definitions for a single class at compile time. This in turn guarantees that the class files generated by compilation, and the behavior of the resulting program, are independent of the order in which the source files are passed to the compiler. 教训很清楚:永远不要将多个顶层类或接口放在一个源文件中。遵循此规则可以确保在编译时单个类不能拥有多个定义。这反过来保证了编译所生成的类文件,以及程序的行为,是独立于源代码文件传递给编译器的顺序的。 diff --git a/Chapter-5/Chapter-5-Item-26-Do-not-use-raw-types.md b/Chapter-5/Chapter-5-Item-26-Do-not-use-raw-types.md index ab52579..defd4d6 100644 --- a/Chapter-5/Chapter-5-Item-26-Do-not-use-raw-types.md +++ b/Chapter-5/Chapter-5-Item-26-Do-not-use-raw-types.md @@ -50,7 +50,7 @@ As mentioned throughout this book, it pays to discover errors as soon as possibl With generics, the type declaration contains the information, not the comment: -对于泛型,类型声明应该包含类型信息,而不是注释: +对于泛型,类型声明应该包含类型信息,而不是注释: ``` // Parameterized collection type - typesafe @@ -100,7 +100,7 @@ private static void unsafeAdd(List list, Object o) { This program compiles, but because it uses the raw type List, you get a warning: -该程序可以编译,但因为它使用原始类型 List,所以你会得到一个警告: +该程序可以编译,但因为它使用原始类型 List,所以你会得到一个警告: ``` Test.java:10: warning: [unchecked] unchecked call to add(E) as a @@ -126,7 +126,7 @@ unsafeAdd(strings, Integer.valueOf(42)); You might be tempted to use a raw type for a collection whose element type is unknown and doesn’t matter. For example, suppose you want to write a method that takes two sets and returns the number of elements they have in common. Here’s how you might write such a method if you were new to generics: -对于元素类型未知且无关紧要的集合,你可能会尝试使用原始类型。例如,假设你希望编写一个方法,该方法接受两个集合并返回它们共有的元素数量。如果你是使用泛型的新手,那么你可以这样编写一个方法: +对于元素类型未知且无关紧要的集合,你可能会尝试使用原始类型。例如,假设你希望编写一个方法,该方法接受两个集合并返回它们共有的元素数量。如果你是使用泛型的新手,那么你可以这样编写一个方法: ``` // Use of raw type for unknown element type - don't do this! @@ -190,7 +190,7 @@ In summary, using raw types can lead to exceptions at runtime, so don’t use th For quick reference, the terms introduced in this item (and a few introduced later in this chapter) are summarized in the following table: -为便于参考,本条目中介绍的术语(以及后面将要介绍的一些术语)总结如下: +为便于参考,本条目中介绍的术语(以及后面将要介绍的一些术语)总结如下: | Term | Example | Item | |:-------:|:-------:|:-------:| diff --git a/Chapter-5/Chapter-5-Item-28-Prefer-lists-to-arrays.md b/Chapter-5/Chapter-5-Item-28-Prefer-lists-to-arrays.md index c57f6fb..44730b2 100644 --- a/Chapter-5/Chapter-5-Item-28-Prefer-lists-to-arrays.md +++ b/Chapter-5/Chapter-5-Item-28-Prefer-lists-to-arrays.md @@ -91,7 +91,7 @@ public class Chooser { To use this class, you have to cast the choose method’s return value from Object to the desired type every time you use invoke the method, and the cast will fail at runtime if you get the type wrong. Taking the advice of Item 29 to heart, we attempt to modify Chooser to make it generic. Changes are shown in boldface: -要使用这个类,每次使用方法调用时,必须将 choose 方法的返回值从对象转换为所需的类型,如果类型错误,转换将在运行时失败。我们认真考虑了 [Item-29](https://github.com/clxering/Effective-Java-3rd-edition-Chinese-English-bilingual/blob/master/Chapter-5/Chapter-5-Item-29-Favor-generic-types.md) 的建议,试图对 Chooser 进行修改,使其具有通用性。变化以粗体显示: +要使用这个类,每次使用方法调用时,必须将 choose 方法的返回值从对象转换为所需的类型,如果类型错误,转换将在运行时失败。我们认真考虑了 [Item-29](https://github.com/clxering/Effective-Java-3rd-edition-Chinese-English-bilingual/blob/master/Chapter-5/Chapter-5-Item-29-Favor-generic-types.md) 的建议,试图对 Chooser 进行修改,使其具有通用性。变化以粗体显示: ``` // A first cut at making Chooser generic - won't compile @@ -142,7 +142,7 @@ The compiler is telling you that it can’t vouch for the safety of the cast at To eliminate the unchecked cast warning, use a list instead of an array. Here is a version of the Chooser class that compiles without error or warning: -若要消除 unchecked 强制转换警告,请使用 list 而不是数组。下面是编译时没有错误或警告的 Chooser 类的一个版本: +若要消除 unchecked 强制转换警告,请使用 list 而不是数组。下面是编译时没有错误或警告的 Chooser 类的一个版本: ``` // List-based Chooser - typesafe diff --git a/Chapter-5/Chapter-5-Item-29-Favor-generic-types.md b/Chapter-5/Chapter-5-Item-29-Favor-generic-types.md index c434d15..c36b476 100644 --- a/Chapter-5/Chapter-5-Item-29-Favor-generic-types.md +++ b/Chapter-5/Chapter-5-Item-29-Favor-generic-types.md @@ -47,7 +47,7 @@ public class Stack { This class should have been parameterized to begin with, but since it wasn’t, we can generify it after the fact. In other words, we can parameterize it without harming clients of the original non-parameterized version. As it stands, the client has to cast objects that are popped off the stack, and those casts might fail at runtime. The first step in generifying a class is to add one or more type parameters to its declaration. In this case there is one type parameter, representing the element type of the stack, and the conventional name for this type parameter is E (Item 68). -这个类一开始就应该是参数化的,但是因为它不是参数化的,所以我们可以在事后对它进行泛化。换句话说,我们可以对它进行参数化,而不会损害原始非参数化版本的客户端。按照目前的情况,客户机必须转换从堆栈中弹出的对象,而这些转换可能在运行时失败。生成类的第一步是向其声明中添加一个或多个类型参数。在这种情况下,有一个类型参数,表示堆栈的元素类型,这个类型参数的常规名称是 E([Item-68](https://github.com/clxering/Effective-Java-3rd-edition-Chinese-English-bilingual/blob/master/Chapter-9/Chapter-9-Item-68-Adhere-to-generally-accepted-naming-conventions.md))。 +这个类一开始就应该是参数化的,但是因为它不是参数化的,所以我们可以在事后对它进行泛化。换句话说,我们可以对它进行参数化,而不会损害原始非参数化版本的客户端。按照目前的情况,客户端必须转换从堆栈中弹出的对象,而这些转换可能在运行时失败。生成类的第一步是向其声明中添加一个或多个类型参数。在这种情况下,有一个类型参数,表示堆栈的元素类型,这个类型参数的常规名称是 E([Item-68](https://github.com/clxering/Effective-Java-3rd-edition-Chinese-English-bilingual/blob/master/Chapter-9/Chapter-9-Item-68-Adhere-to-generally-accepted-naming-conventions.md))。 The next step is to replace all the uses of the type Object with the appropriate type parameter and then try to compile the resulting program: @@ -102,7 +102,7 @@ elements = (E[]) new Object[DEFAULT_INITIAL_CAPACITY]; The compiler may not be able to prove that your program is typesafe, but you can. You must convince yourself that the unchecked cast will not compromise the type safety of the program. The array in question (elements) is stored in a private field and never returned to the client or passed to any other method. The only elements stored in the array are those passed to the push method, which are of type E, so the unchecked cast can do no harm. -编译器可能无法证明你的程序是类型安全的,但你可以。你必须说服自己,unchecked 的转换不会损害程序的类型安全性。所涉及的数组(元素)存储在私有字段中,从未返回给客户机或传递给任何其他方法。数组中存储的惟一元素是传递给 push 方法的元素,它们属于 E 类型,因此 unchecked 的转换不会造成任何损害。 +编译器可能无法证明你的程序是类型安全的,但你可以。你必须说服自己,unchecked 的转换不会损害程序的类型安全性。所涉及的数组(元素)存储在私有字段中,从未返回给客户端或传递给任何其他方法。数组中存储的惟一元素是传递给 push 方法的元素,它们属于 E 类型,因此 unchecked 的转换不会造成任何损害。 Once you’ve proved that an unchecked cast is safe, suppress the warning in as narrow a scope as possible (Item 27). In this case, the constructor contains only the unchecked array creation, so it’s appropriate to suppress the warning in the entire constructor. With the addition of an annotation to do this, Stack compiles cleanly, and you can use it without explicit casts or fear of a ClassCastException: @@ -190,7 +190,7 @@ class DelayQueue implements BlockingQueue The type parameter list () requires that the actual type parameter E be a subtype of java.util.concurrent.Delayed. This allows the DelayQueue implementation and its clients to take advantage of Delayed methods on the elements of a DelayQueue, without the need for explicit casting or the risk of a ClassCastException. The type parameter E is known as a bounded type parameter. Note that the subtype relation is defined so that every type is a subtype of itself [JLS, 4.10], so it is legal to create a DelayQueue. -类型参数列表()要求实际的类型参数 E 是 java.util.concurrent.Delayed 的一个子类型。这允许 DelayQueue 实现及其客户机利用 DelayQueue 元素上的 Delayed 方法,而不需要显式转换或 ClassCastException 的风险。类型参数 E 称为有界类型参数。注意,子类型关系的定义使得每个类型都是它自己的子类型 [JLS, 4.10],所以创建 `DelayQueue` 是合法的。 +类型参数列表()要求实际的类型参数 E 是 java.util.concurrent.Delayed 的一个子类型。这允许 DelayQueue 实现及其客户端利用 DelayQueue 元素上的 Delayed 方法,而不需要显式转换或 ClassCastException 的风险。类型参数 E 称为有界类型参数。注意,子类型关系的定义使得每个类型都是它自己的子类型 [JLS, 4.10],所以创建 `DelayQueue` 是合法的。 In summary, generic types are safer and easier to use than types that require casts in client code. When you design new types, make sure that they can be used without such casts. This will often mean making the types generic. If you have any existing types that should be generic but aren’t, generify them. This will make life easier for new users of these types without breaking existing clients (Item 26). diff --git a/Chapter-5/Chapter-5-Item-31-Use-bounded-wildcards-to-increase-API-flexibility.md b/Chapter-5/Chapter-5-Item-31-Use-bounded-wildcards-to-increase-API-flexibility.md index 8e5872c..b7e2f3b 100644 --- a/Chapter-5/Chapter-5-Item-31-Use-bounded-wildcards-to-increase-API-flexibility.md +++ b/Chapter-5/Chapter-5-Item-31-Use-bounded-wildcards-to-increase-API-flexibility.md @@ -56,7 +56,7 @@ cannot be converted to Iterable Luckily, there’s a way out. The language provides a special kind of parameterized type call a bounded wildcard type to deal with situations like this. The type of the input parameter to pushAll should not be “Iterable of E” but “Iterable of some subtype of E,” and there is a wildcard type that means precisely that: Iterable. (The use of the keyword extends is slightly misleading: recall from Item 29 that subtype is defined so that every type is a subtype of itself, even though it does not extend itself.) Let’s modify pushAll to use this type: -幸运的是,有一种解决方法。Java 提供了一种特殊的参数化类型,`有界通配符类型`来处理这种情况。pushAll 的输入参数的类型不应该是「E 的 Iterable 接口」,而应该是「E 的某个子类型的 Iterable 接口」,并且有一个通配符类型,它的确切含义是:`Iterable`(关键字 extends 的使用稍微有些误导:回想一下第 29 项,定义了子类型,以便每个类型都是其本身的子类型,即使它没有扩展自己。)让我们修改 pushAll 来使用这种类型: +幸运的是,有一种解决方法。Java 提供了一种特殊的参数化类型,`有界通配符类型`来处理这种情况。pushAll 的输入参数的类型不应该是「E 的 Iterable 接口」,而应该是「E 的某个子类型的 Iterable 接口」,并且有一个通配符类型,它的确切含义是:`Iterable`(关键字 extends 的使用稍微有些误导:回想一下 [Item-29](https://github.com/clxering/Effective-Java-3rd-edition-Chinese-English-bilingual/blob/master/Chapter-5/Chapter-5-Item-29-Favor-generic-types.md),定义了子类型,以便每个类型都是其本身的子类型,即使它没有扩展自己。)让我们修改 pushAll 来使用这种类型: ``` // Wildcard type for a parameter that serves as an E producer @@ -274,7 +274,7 @@ private static void swapHelper(List list, int i, int j) { The swapHelper method knows that list is a `List`. Therefore, it knows that any value it gets out of this list is of type E and that it’s safe to put any value of type E into the list. This slightly convoluted implementation of swap compiles cleanly. It allows us to export the nice wildcard-based declaration, while taking advantage of the more complex generic method internally. Clients of the swap method don’t have to confront the more complex swapHelper declaration, but they do benefit from it. It is worth noting that the helper method has precisely the signature that we dismissed as too complex for the public method. -swapHelper 方法知道 list 是一个 `List`。因此,它知道它从这个列表中得到的任何值都是 E 类型的,并且将 E 类型的任何值放入这个列表中都是安全的。这个稍微复杂的实现能够正确编译。它允许我们导出基于 通配符的声明,同时在内部利用更复杂的泛型方法。swap 方法的客户机不必面对更复杂的 swapHelper 声明,但它们确实从中受益。值得注意的是,helper 方法具有我们认为对于公共方法过于复杂而忽略的签名。 +swapHelper 方法知道 list 是一个 `List`。因此,它知道它从这个列表中得到的任何值都是 E 类型的,并且将 E 类型的任何值放入这个列表中都是安全的。这个稍微复杂的实现能够正确编译。它允许我们导出基于 通配符的声明,同时在内部利用更复杂的泛型方法。swap 方法的客户端不必面对更复杂的 swapHelper 声明,但它们确实从中受益。值得注意的是,helper 方法具有我们认为对于公共方法过于复杂而忽略的签名。 In summary, using wildcard types in your APIs, while tricky, makes the APIs far more flexible. If you write a library that will be widely used, the proper use of wildcard types should be considered mandatory. Remember the basic rule: producer-extends, consumer-super (PECS). Also remember that all comparables and comparators are consumers. diff --git a/Chapter-6/Chapter-6-Item-34-Use-enums-instead-of-int-constants.md b/Chapter-6/Chapter-6-Item-34-Use-enums-instead-of-int-constants.md index 21971d5..57e0ad9 100644 --- a/Chapter-6/Chapter-6-Item-34-Use-enums-instead-of-int-constants.md +++ b/Chapter-6/Chapter-6-Item-34-Use-enums-instead-of-int-constants.md @@ -146,7 +146,7 @@ Weight on NEPTUNE is 210.208751 Until 2006, two years after enums were added to Java, Pluto was a planet. This raises the question “what happens when you remove an element from an enum type?” The answer is that any client program that doesn’t refer to the removed element will continue to work fine. So, for example, our WeightTable program would simply print a table with one fewer row. And what of a client program that refers to the removed element (in this case, Planet.Pluto)? If you recompile the client program, the compilation will fail with a helpful error message at the line that refers to the erstwhile planet; if you fail to recompile the client, it will throw a helpful exception from this line at runtime. This is the best behavior you could hope for, far better than what you’d get with the int enum pattern. -直到 2006 年,也就是枚举被添加到 Java 的两年后,冥王星还是一颗行星。这就提出了一个问题:「从枚举类型中删除元素时会发生什么?」答案是,任何不引用被删除元素的客户端程序将继续正常工作。例如,我们的 WeightTable 程序只需打印一个少一行的表。那么引用被删除元素(在本例中是 Planet.Pluto)的客户端程序又如何呢?如果重新编译客户端程序,编译将失败,并在引用该「过时」行星的行中显示一条有用的错误消息;如果你未能重新编译客户端,它将在运行时从这行抛出一个有用的异常。这是你所希望的最佳行为,比 int 枚举模式要好得多。 +直到 2006 年,也就是枚举被添加到 Java 的两年后,冥王星还是一颗行星。这就提出了一个问题:「从枚举类型中删除元素时会发生什么?」答案是,任何不引用被删除元素的客户端程序将继续正常工作。例如,我们的 WeightTable 程序只需打印一个少一行的表。那么引用被删除元素(在本例中是 Planet.Pluto)的客户端程序又如何呢?如果重新编译客户端程序,编译将失败,并在引用该「过时」行星的行中显示一条有用的错误消息;如果你未能重新编译客户端,它将在运行时从这行抛出一个有用的异常。这是你所希望的最佳行为,比 int 枚举模式要好得多。 Some behaviors associated with enum constants may need to be used only from within the class or package in which the enum is defined. Such behaviors are best implemented as private or package-private methods. Each constant then carries with it a hidden collection of behaviors that allows the class or package containing the enum to react appropriately when presented with the constant. Just as with other classes, unless you have a compelling reason to expose an enum method to its clients, declare it private or, if need be, package-private (Item 15). diff --git a/Chapter-6/Chapter-6-Item-35-Use-instance-fields-instead-of-ordinals.md b/Chapter-6/Chapter-6-Item-35-Use-instance-fields-instead-of-ordinals.md index 9ea26ca..0ac9535 100644 --- a/Chapter-6/Chapter-6-Item-35-Use-instance-fields-instead-of-ordinals.md +++ b/Chapter-6/Chapter-6-Item-35-Use-instance-fields-instead-of-ordinals.md @@ -39,4 +39,4 @@ public enum Ensemble { The Enum specification has this to say about ordinal: “Most programmers will have no use for this method. It is designed for use by general-purpose enumbased data structures such as EnumSet and EnumMap.” Unless you are writing code with this character, you are best off avoiding the ordinal method entirely. -枚举规范对 ordinal 方法的评价是这样的:「大多数程序员都不会去使用这个方法。它是为基于枚举的通用数据结构(如 EnumSet 和 EnumMap)而设计的」。除非你使用这个数据结构编写代码,否则最好完全避免使用这个方法。 +枚举规范对 ordinal 方法的评价是这样的:「大多数程序员都不会去使用这个方法。它是为基于枚举的通用数据结构(如 EnumSet 和 EnumMap)而设计的」。除非你使用这个数据结构编写代码,否则最好完全避免使用这个方法。 From 9c2885fd439b1fb232432b3a1c6337170e6c4fae Mon Sep 17 00:00:00 2001 From: clxeringlab Date: Fri, 15 Feb 2019 19:47:22 +0800 Subject: [PATCH 03/18] init Item 37 --- ...em-36-Use-EnumSet-instead-of-bit-fields.md | 6 +- ...Use-EnumMap-instead-of-ordinal-indexing.md | 101 ++++++++++++++++-- README.md | 2 +- 3 files changed, 96 insertions(+), 13 deletions(-) diff --git a/Chapter-6/Chapter-6-Item-36-Use-EnumSet-instead-of-bit-fields.md b/Chapter-6/Chapter-6-Item-36-Use-EnumSet-instead-of-bit-fields.md index 61c4210..3af181d 100644 --- a/Chapter-6/Chapter-6-Item-36-Use-EnumSet-instead-of-bit-fields.md +++ b/Chapter-6/Chapter-6-Item-36-Use-EnumSet-instead-of-bit-fields.md @@ -28,11 +28,11 @@ text.applyStyles(STYLE_BOLD | STYLE_ITALIC); The bit field representation also lets you perform set operations such as union and intersection efficiently using bitwise arithmetic. But bit fields have all the disadvantages of int enum constants and more. It is even harder to interpret a bit field than a simple int enum constant when it is printed as a number. There is no easy way to iterate over all of the elements represented by a bit field. Finally, you have to predict the maximum number of bits you’ll ever need at the time you’re writing the API and choose a type for the bit field (typically int or long) accordingly. Once you’ve picked a type, you can’t exceed its width (32 or 64 bits) without changing the API. -位字段表示方式允许使用位运算高效地执行 Set 操作,如并集和交集。但是位字段具有 int 枚举常量等所有缺点。当位字段被打印为数字时,它比简单的 int 枚举常量更难理解。没有一种简单的方法可以遍历由位字段表示的所有元素。最后,你必须预测在编写 API 时需要的最大位数,并相应地为位字段(通常是 int 或 long)选择一种类型。一旦选择了一种类型,在不更改 API 的情况下,不能超过它的宽度(32 或 64 位)。 +位字段表示方式允许使用位运算高效地执行 Set 操作,如并集和交集。但是位字段具有 int 枚举常量所有缺点,甚至更多。当位字段被打印为数字时,它比简单的 int 枚举常量更难理解。没有一种简单的方法可以遍历由位字段表示的所有元素。最后,你必须预测在编写 API 时需要的最大位数,并相应地为位字段(通常是 int 或 long)选择一种类型。一旦选择了一种类型,在不更改 API 的情况下,不能超过它的宽度(32 或 64 位)。 Some programmers who use enums in preference to int constants still cling to the use of bit fields when they need to pass around sets of constants. There is no reason to do this, because a better alternative exists. The java.util package provides the EnumSet class to efficiently represent sets of values drawn from a single enum type. This class implements the Set interface, providing all of the richness, type safety, and interoperability you get with any other Set implementation. But internally, each EnumSet is represented as a bit vector. If the underlying enum type has sixty-four or fewer elements—and most do—the entire EnumSet is represented with a single long, so its performance is comparable to that of a bit field. Bulk operations, such as removeAll and retainAll, are implemented using bitwise arithmetic, just as you’d do manually for bit fields. But you are insulated from the ugliness and errorproneness of manual bit twiddling: the EnumSet does the hard work for you. -一些使用枚举而不是 int 常量的程序员在需要传递常量集时仍然坚持使用位字段。没有理由这样做,因为存在更好的选择。java.util 包提供 EnumSet 类来有效地表示从单个枚举类型中提取的值集。这个类实现了 Set 接口,提供了所有其他 Set 实现所具有的丰富性、类型安全性和互操作性。但在内部,每个 EnumSet 都表示为一个位向量。如果底层枚举类型有 64 个或更少的元素(大多数都是),则整个 EnumSet 用一个 long 表示,因此其性能与位字段的性能相当。批量操作(如 removeAll 和 retainAll)是使用逐位算法实现的,就像手动处理位字段一样。但是,你可以避免因手工修改导致产生不良代码和潜在错误:EnumSet 为你完成了这些繁重的工作。 +一些使用枚举而不是 int 常量的程序员在需要传递常量集时仍然坚持使用位字段。没有理由这样做,因为存在更好的选择。`java.util` 包提供 EnumSet 类来有效地表示从单个枚举类型中提取的值集。这个类实现了 Set 接口,提供了所有其他 Set 实现所具有的丰富性、类型安全性和互操作性。但在内部,每个 EnumSet 都表示为一个位向量。如果底层枚举类型有 64 个或更少的元素(大多数都是),则整个 EnumSet 用一个 long 表示,因此其性能与位字段的性能相当。批量操作(如 removeAll 和 retainAll)是使用逐位算法实现的,就像手动处理位字段一样。但是,你可以避免因手工修改导致产生不良代码和潜在错误:EnumSet 为你完成了这些繁重的工作。 Here is how the previous example looks when modified to use enums and enum sets instead of bit fields. It is shorter, clearer, and safer: @@ -61,4 +61,4 @@ Note that the applyStyles method takes a `Set