第2章 创建和销毁对象
控制构造入口、依赖、实例数量、对象分配、引用寿命与资源关闭。覆盖10个正式目录节点,并通过决策图、取舍实验和反例证据复核。
为什么“第2章 创建和销毁对象”必须从情境与取舍开始
控制构造入口、依赖、实例数量、对象分配、引用寿命与资源关闭。本页属于正式单元,目标不是复制原书短文,而是依照 InformIT/Addison-Wesley 官方第3版详细目录,把每个Item重构成情境、备选方案、最小反例、验证和决议。正式节点共10个,最终产物是构造策略矩阵、对象生命周期图与资源泄漏测试。
Effective Java给的是工程建议,不是语言规范。建议通常优化长期API、类型安全、可维护性或正确性,但可能增加对象、样板、迁移或运行成本。高质量应用必须先声明调用者、API寿命、威胁模型和性能预算,再解释为什么本Item在这个情境中优于替代方案。
版次坐标与现代边界
本课程采用 Joshua Bloch 的 Effective Java, Third Edition,Addison-Wesley Professional,2017年12月出版,版权2018,416页,ISBN 9780134685991。官方详细目录确认12章、90条Item,另有第二版条目对照、参考文献与索引;课程覆盖15个正式单元、105个目录节点,另设学习地图和综合验收,共17页。
原书覆盖至Java 9。课程保持Item原义和编号,同时把Java 25作为复核边界:record、sealed class、模式匹配、虚拟线程、FFM等可能改变实现手段,但不会自动证明原建议失效。任何现代补充都要标注“原书建议”“后续语言/API变化”和“本项目决策”三层,避免伪装成原文。
五个贯穿全书的术语
、、、、。
适用前提限定建议范围,备选方案防止二元思维,边界反例测试结论,兼容触点量化演化成本,决策记录让未来维护者能重放推理。
四个可复算指标
Item覆盖检查90条建议是否都有决策落点:
反例覆盖同时考虑正常、边界、失败和替代方案:
API兼容触点可粗略表示为公开表面积与调用方乘积:
决策风险由影响面、未验证比例与可变共享状态放大:
这些数值用于定位缺口,不证明设计优越。Item覆盖100%可能只是机械同意;兼容触点少也可能来自功能不足。必须同时保存反例、被放弃方案和质性理由。
Chapter 2: Creating and Destroying Objects
正式节点 1/10。 先声明调用方、API寿命、Java版本、性能/安全约束和允许破坏范围,再判断该建议是否适用。 “Chapter 2: Creating and Destroying Objects”必须服务于“控制构造入口、依赖、实例数量、对象分配、引用寿命与资源关闭”,并在“构造策略矩阵、对象生命周期图与资源泄漏测试”中留下决策和证据。
至少执行原方案、采用建议、边界反例三组对照,说明收益由谁获得、成本由谁承担、Java 9到Java 25是否改变前提。若出现“追求语法简短而制造非法中间状态、硬编码依赖、陈旧引用或不可预测清理”,就调整适用域或拒绝建议,不能把Item编号当作论证本身。
Item 1: Consider static factory methods instead of constructors
正式节点 2/10。 列出原方案、Item建议与至少一个替代方案,比较类型安全、可变性、兼容性、资源和认知成本。 “Item 1: Consider static factory methods instead of constructors”必须服务于“控制构造入口、依赖、实例数量、对象分配、引用寿命与资源关闭”,并在“构造策略矩阵、对象生命周期图与资源泄漏测试”中留下决策和证据。
至少执行原方案、采用建议、边界反例三组对照,说明收益由谁获得、成本由谁承担、Java 9到Java 25是否改变前提。若出现“追求语法简短而制造非法中间状态、硬编码依赖、陈旧引用或不可预测清理”,就调整适用域或拒绝建议,不能把Item编号当作论证本身。
Item 2: Consider a builder when faced with many constructor parameters
正式节点 3/10。 构造能推翻过度概括的最小反例,区分语言保证、库合同、实现偶然和项目策略。 “Item 2: Consider a builder when faced with many constructor parameters”必须服务于“控制构造入口、依赖、实例数量、对象分配、引用寿命与资源关闭”,并在“构造策略矩阵、对象生命周期图与资源泄漏测试”中留下决策和证据。
至少执行原方案、采用建议、边界反例三组对照,说明收益由谁获得、成本由谁承担、Java 9到Java 25是否改变前提。若出现“追求语法简短而制造非法中间状态、硬编码依赖、陈旧引用或不可预测清理”,就调整适用域或拒绝建议,不能把Item编号当作论证本身。
Item 3: Enforce the singleton property with a private constructor or an enum type
正式节点 4/10。 把建议落到可编译客户端、性质测试、突变/失败注入或基准,避免用代码风格偏好替代证据。 “Item 3: Enforce the singleton property with a private constructor or an enum type”必须服务于“控制构造入口、依赖、实例数量、对象分配、引用寿命与资源关闭”,并在“构造策略矩阵、对象生命周期图与资源泄漏测试”中留下决策和证据。
至少执行原方案、采用建议、边界反例三组对照,说明收益由谁获得、成本由谁承担、Java 9到Java 25是否改变前提。若出现“追求语法简短而制造非法中间状态、硬编码依赖、陈旧引用或不可预测清理”,就调整适用域或拒绝建议,不能把Item编号当作论证本身。
Item 4: Enforce noninstantiability with a private constructor
正式节点 5/10。 记录采用后公开表面积、迁移成本、失败语义和回滚路径,让下一次版本升级能复查原决策。 “Item 4: Enforce noninstantiability with a private constructor”必须服务于“控制构造入口、依赖、实例数量、对象分配、引用寿命与资源关闭”,并在“构造策略矩阵、对象生命周期图与资源泄漏测试”中留下决策和证据。
至少执行原方案、采用建议、边界反例三组对照,说明收益由谁获得、成本由谁承担、Java 9到Java 25是否改变前提。若出现“追求语法简短而制造非法中间状态、硬编码依赖、陈旧引用或不可预测清理”,就调整适用域或拒绝建议,不能把Item编号当作论证本身。
Item 5: Prefer dependency injection to hardwiring resources
正式节点 6/10。 检查本Item引用的其他Item和潜在冲突;局部最优若扩大整体API或共享状态,应回到全局合同裁决。 “Item 5: Prefer dependency injection to hardwiring resources”必须服务于“控制构造入口、依赖、实例数量、对象分配、引用寿命与资源关闭”,并在“构造策略矩阵、对象生命周期图与资源泄漏测试”中留下决策和证据。
至少执行原方案、采用建议、边界反例三组对照,说明收益由谁获得、成本由谁承担、Java 9到Java 25是否改变前提。若出现“追求语法简短而制造非法中间状态、硬编码依赖、陈旧引用或不可预测清理”,就调整适用域或拒绝建议,不能把Item编号当作论证本身。
Item 6: Avoid creating unnecessary objects
正式节点 7/10。 先声明调用方、API寿命、Java版本、性能/安全约束和允许破坏范围,再判断该建议是否适用。 “Item 6: Avoid creating unnecessary objects”必须服务于“控制构造入口、依赖、实例数量、对象分配、引用寿命与资源关闭”,并在“构造策略矩阵、对象生命周期图与资源泄漏测试”中留下决策和证据。
至少执行原方案、采用建议、边界反例三组对照,说明收益由谁获得、成本由谁承担、Java 9到Java 25是否改变前提。若出现“追求语法简短而制造非法中间状态、硬编码依赖、陈旧引用或不可预测清理”,就调整适用域或拒绝建议,不能把Item编号当作论证本身。
Item 7: Eliminate obsolete object references
正式节点 8/10。 列出原方案、Item建议与至少一个替代方案,比较类型安全、可变性、兼容性、资源和认知成本。 “Item 7: Eliminate obsolete object references”必须服务于“控制构造入口、依赖、实例数量、对象分配、引用寿命与资源关闭”,并在“构造策略矩阵、对象生命周期图与资源泄漏测试”中留下决策和证据。
至少执行原方案、采用建议、边界反例三组对照,说明收益由谁获得、成本由谁承担、Java 9到Java 25是否改变前提。若出现“追求语法简短而制造非法中间状态、硬编码依赖、陈旧引用或不可预测清理”,就调整适用域或拒绝建议,不能把Item编号当作论证本身。
Item 8: Avoid finalizers and cleaners
正式节点 9/10。 构造能推翻过度概括的最小反例,区分语言保证、库合同、实现偶然和项目策略。 “Item 8: Avoid finalizers and cleaners”必须服务于“控制构造入口、依赖、实例数量、对象分配、引用寿命与资源关闭”,并在“构造策略矩阵、对象生命周期图与资源泄漏测试”中留下决策和证据。
至少执行原方案、采用建议、边界反例三组对照,说明收益由谁获得、成本由谁承担、Java 9到Java 25是否改变前提。若出现“追求语法简短而制造非法中间状态、硬编码依赖、陈旧引用或不可预测清理”,就调整适用域或拒绝建议,不能把Item编号当作论证本身。
Item 9: Prefer try-with-resources to try-finally
正式节点 10/10。 把建议落到可编译客户端、性质测试、突变/失败注入或基准,避免用代码风格偏好替代证据。 “Item 9: Prefer try-with-resources to try-finally”必须服务于“控制构造入口、依赖、实例数量、对象分配、引用寿命与资源关闭”,并在“构造策略矩阵、对象生命周期图与资源泄漏测试”中留下决策和证据。
至少执行原方案、采用建议、边界反例三组对照,说明收益由谁获得、成本由谁承担、Java 9到Java 25是否改变前提。若出现“追求语法简短而制造非法中间状态、硬编码依赖、陈旧引用或不可预测清理”,就调整适用域或拒绝建议,不能把Item编号当作论证本身。
建立Item决策档案
每条Item建立一张记录:问题情境、调用方、原方案、建议方案、替代方案、收益、代价、最小反例、验证类型、Java版本和回滚条件。若多个Item同时作用,例如“不可变性”与“构建器”、“接口”与“反射”、“失败原子性”与“防御复制”,要画出依赖和冲突,按全局API合同裁决。
还要做版本三角复核。第一角是Java 9原书语境,第二角是Java 25语言/API能力,第三角是当前项目约束。新特性可能降低建议成本,例如record帮助不可变值;也可能引入新边界,例如虚拟线程不会消除共享状态竞态。只有三角中的差异被测试,才能决定“原样采用、现代化实现、缩小适用域或拒绝”。
本单元的最小示例是独立教学重构:
final class Client {
private final java.net.http.HttpClient http;
private Client(java.net.http.HttpClient http) { this.http = java.util.Objects.requireNonNull(http); }
static Client of(java.net.http.HttpClient http) { return new Client(http); }
}
var client = Client.of(java.net.http.HttpClient.newHttpClient());客户端测试要在公共边界验证行为,而不是复制实现:
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;
class ItemDecisionTest {
@Test void alternativeBoundaryAndRollbackStayVisible() {
var evidence = java.util.List.of("baseline", "item", "counterexample", "rollback");
assertAll(
() -> assertEquals(4, evidence.size()),
() -> assertTrue(evidence.contains("counterexample")),
() -> assertTrue(evidence.indexOf("baseline") < evidence.indexOf("item"))
);
}
}交接标准要求另一位评审者重放决策:
Feature: Chapter 2: Creating and Destroying Objects
Scenario: reproduce an Item decision
Given the Java version, caller contract, and original alternative
When a reviewer runs the baseline, recommended design, and counterexample
Then benefits, costs, compatibility impact, and failure semantics are visible
And the reviewer can keep, narrow, or reject the advice with evidence先预测再运行
先预测:公开成员从12增至30、可变状态从35%增至80%、调用方从8增至24时,兼容触点、状态风险、测试下界和演化成本怎样变化?一次只改变一个变量,观察编译客户端数、突变失败、并发交错、分配与P95延迟。数据偏离预测时,先检查调用方假设、Item冲突、实现偶然和Java版本变化。
三类高频误区
本章回顾
“第2章 创建和销毁对象”的核心是把“控制构造入口、依赖、实例数量、对象分配、引用寿命与资源关闭”转成可复核工程决策。完整链路包含适用前提、备选方案、Item原义、现代版本差异、边界反例、可编译客户端、度量与回滚;缺少任一环,都只是对建议的表态,而非设计证据。