在Java中,对象从状态是否可被修改的角度可以分为可变对象与不可变对象。可变对象在创建之后,其内部的字段值仍然可以通过公开的方法或者直接访问被更改;而不可变对象在构造完成之后,对外不提供任何修改自身状态的方式,任何看起来像修改的操作实际上都返回了一个新的对象实例。理解这两类对象的本质区别,是写出健壮、可维护以及线程安全代码的基础。

从内存与引用角度看可变与不可变
当我们说一个对象是「可变」的时候,核心含义是:在堆内存中该对象实例的数据内容可以被改变,而栈上的多个引用变量可以指向同一个堆实例。例如有一个普通的User类,它包含可写的name字段,那么通过任意一个引用调用setName方法,都会修改那一块共享的堆内存,其他引用立刻就能观察到变化。这种共享可变状态在单线程里方便,但在多线程里极易引发问题。
不可变对象则走了另一条路。以java.lang.String为例,字符串底层使用final字符数组并且不对外暴露修改入口,所有substring、concat等操作都返回新的String实例。也就是说,原来的对象在内存中纹丝不动,变化通过「新建对象」来体现。这样做虽然会带来一定的对象创建开销,但换来了引用透明性:只要拿到一个引用,就可以放心地认为它代表的值永远不变。
从JVM内存模型层面看,可变对象如果未被正确同步,一个线程的写入对其他线程可能不可见,甚至看到部分构造的状态;不可变对象由于状态固定,只要构造过程本身是安全的(没有this引用逸出),发布之后不需要额外同步就能被所有线程安全共享。这也是为什么许多并发框架都推荐尽量使用不可变数据载体。
如何编写一个标准的不可变类
在Java中把一个类设计成不可变,需要遵循几条硬性规则。第一,类本身应当用final修饰,防止被继承后通过重写方法破坏不变性约束。第二,所有字段必须是private且final,并在构造器中完成全部初始化。第三,不提供任何setter或者其他能修改字段的方法。第四,如果字段引用了可变对象(例如一个List),那么在构造时应当进行防御性拷贝,在返回时也要返回拷贝而非原引用。
下面给出一个符合规范的不可变类示例,其中对传入的列表做了深一层保护,避免外部代码后续修改影响内部状态:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public final class ImmutableOrder {
private final long id;
private final List<String> items;
public ImmutableOrder(long id, List<String> items) {
this.id = id;
// 防御性拷贝,避免外部列表被修改
this.items = Collections.unmodifiableList(new ArrayList<>(items));
}
public long getId() {
return id;
}
public List<String> getItems() {
// 返回不可修改视图,防止调用方改内部集合
return items;
}
}
上面的代码里,Collections.unmodifiableList返回的是一个包装器,它阻止了对原列表的增删改,但并不阻止原列表引用本身被替换,所以配合final字段才是完整的保护。如果字段是基本类型或真正不可变的引用(如String),则不需要这么麻烦的拷贝逻辑。
与之相对,可变对象只要去掉final、提供setter即可。虽然写起来简单,但在方法间传递时,要时刻警惕「谁改了我的数据」。一种常见实践是:对内部状态只返回拷贝,或者明确在文档里声明该方法会修改入参,从而降低协作成本。
可变与不可变对象的适用场景及权衡
不可变对象最适合用作值对象、配置项、多线程间传递的消息以及缓存的key。例如在HashMap中做key的String或自定义不可变ID就非常安全;一旦key可变,hashCode变化会导致无法正确取值。又比如在函数式风格代码里,不可变数据让流水线每一步都清晰可预测,不会因为上游偷偷改了集合而让下游出错。
可变对象则在需要频繁修改、且对象较大或生命周期较长时更有优势。比如一个实时游戏里的角色坐标,如果每移动一次都new一个新坐标对象,GC压力会很大;此时用可变Point并在原地更新反而更高效。同理,构建复杂报表时,先用可变集合累积数据,最后包装成不可变视图对外发布,是兼顾性能与安全的常用模式。
在实际架构中,一个稳妥的策略是:对外接口和跨线程边界传递数据时使用不可变对象,进程内部、同一线程的算法细节中合理使用可变对象以减少开销。同时要警惕「伪不可变」——例如字段是final但里面套着一个可变的HashMap,若直接把该map的引用给出去,对象实际上还是可变的。厘清这些边界,才能真正在Java中理解并运用好可变与不可变对象。
mutable_objectimmutable_objectJava_final修改时间:2026-08-16 01:52:12