1. 概述
本文将深入讲解什么是不可变对象,如何在 Java 中实现不可变性,以及这样做的优势。内容简洁实用,适合有一定经验的开发者快速掌握核心要点,避免踩坑。
2. 什么是不可变对象?
一个不可变对象指的是:✅ 对象一旦创建完成,其内部状态就永远不会改变。
这意味着该对象的公共 API 能够保证它在整个生命周期中行为一致,不会出现“中途变卦”的情况。
以 String
类为例,虽然它的 replace
方法看起来像是在修改字符串,但实际上原始对象并未被改动:
String name = "baeldung";
String newName = name.replace("dung", "----");
assertEquals("baeldung", name);
assertEquals("bael----", newName);
⚠️ 关键点:String
的所有“修改”方法其实都返回一个新实例,原对象保持不变。这也是它被设计为不可变类的典型体现。
不可变对象的 API 应该只提供只读方法,❌ 绝不应该包含任何会修改内部状态的操作。
3. Java 中的 final 关键字
在实现不可变性之前,必须搞清楚 final
的真正含义。
Java 中变量默认是可变的 —— 你可以随时更改它所持有的值。但一旦加上 final
,编译器就会阻止你再次赋值:
final String name = "baeldung";
name = "bael..."; // ❌ 编译错误!
⚠️ 重要提醒:final
只保证引用不被重新赋值,并不能防止通过该引用去修改对象内部状态。例如下面这个常见误区:
final List<String> strings = new ArrayList<>();
assertEquals(0, strings.size());
strings.add("baeldung");
assertEquals(1, strings.size()); // ✅ 成功!说明 List 内容变了
尽管 strings
是 final
的,但我们依然可以通过 .add()
修改其内容。因此,ArrayList
本身并不是不可变集合。
4. 如何在 Java 中实现不可变对象
要真正构建一个不可变类,仅靠 final
字段还不够,需要从设计层面严格把控。以下是关键步骤:
✅ 核心原则
- 所有字段用
private final
修饰 - 不提供任何 setter 或修改状态的方法
- 所有方法只能返回数据副本或不变视图
- 构造函数中完成状态初始化,且避免泄露
this
引用 - 如果字段是可变类型(如集合、日期等),必须防御性拷贝
示例:实现一个不可变的 Money 类
class Money {
private final double amount;
private final Currency currency;
public Money(double amount, Currency currency) {
this.amount = amount;
this.currency = currency;
}
public Currency getCurrency() {
return currency;
}
public double getAmount() {
return amount;
}
// 提供操作方法时,返回新对象而不是修改自身
public Money add(Money other) {
if (!this.currency.equals(other.currency)) {
throw new IllegalArgumentException("Currencies must match");
}
return new Money(this.amount + other.amount, this.currency);
}
@Override
public boolean equals(Object o) { /* 省略 equals 实现 */ }
@Override
public int hashCode() { /* 省略 hashCode 实现 */ }
}
⚠️ 踩坑警告:防御性拷贝
如果类中包含可变对象(比如 Date
、Collection
),必须做深拷贝,否则外部仍可能篡改内部状态。
举个反例:
public class Event {
private final Date when;
public Event(Date when) {
this.when = when; // ❌ 危险!外部还能修改 when
}
public Date getWhen() {
return when;
}
}
正确做法是:
public Event(Date when) {
this.when = new Date(when.getTime()); // ✅ 防御性拷贝
}
public Date getWhen() {
return new Date(when.getTime()); // ✅ 返回副本
}
对于集合类型也类似,建议使用 Collections.unmodifiableList
包装:
private final List<String> tags;
public Event(List<String> tags) {
this.tags = List.copyOf(tags); // ✅ Java 10+ 推荐写法
// 或者:new ArrayList<>(tags),再用 Collections.unmodifiableList 包装
}
public List<String> getTags() {
return Collections.unmodifiableList(tags); // ✅ 返回不可变视图
}
5. 不可变对象的优势
不可变对象不是为了炫技,而是解决实际问题的利器。主要好处包括:
- ✅ 线程安全:无需同步控制,可被多个线程并发访问而无需担心状态不一致
- ✅ 避免副作用:函数调用不会意外改变传入的对象,适合函数式编程风格
- ✅ 缓存友好:
hashCode
和equals
结果稳定,适合用作 Map 的 key 或 Set 元素 - ✅ 简化调试:对象状态固定,出问题更容易追踪
- ✅ 天然防御:防止调用方修改内部数据,提升封装性
特别是在高并发场景下,不可变对象能大幅降低锁竞争,提升系统吞吐量。
6. 总结
不可变对象的核心思想很简单:✅ 创建即定型,状态永不变更。
通过合理使用 final
、防御性拷贝和只读 API,我们可以构建出安全、可靠、易于维护的不可变类。它们在多线程环境、缓存机制、领域模型设计中都有广泛应用。
📌 小贴士:JDK 中典型的不可变类包括 String
、Integer
、LocalDateTime
、Collections.unmodifiableXXX
返回的对象等。设计时不妨多参考这些“教科书级”实现。
邮箱联系:dev@baeldung.com
原文来源:Baeldung - Immutable Objects in Java