Java学习随笔:浅拷贝和深拷贝到底差在哪(2026-10-10)
刚学 Java 的时候,我以为对象赋值就是把内容复制一份。写一句 a 等于 b,就觉得两边从此各管各的。直到有一次修改了其中一个对象的字段,另一个对象也跟着变了,我才意识到:这里复制的其实是引用,两个变量指向的是同一块内存。
要理解拷贝,先要分清“引用”和“对象”。对象变量里存的只是一个地址,赋值时复制的是这个地址,而不是对象本身。所以两个变量指向同一个对象时,任何一方修改字段,另一方看到的自然也是修改后的值。这就是所谓的浅拷贝:新对象是有的,但对象内部的引用类型字段依然指向原来的对象。
Java 提供了几种实现拷贝的方式。最直接的是实现 Cloneable 接口并重写 clone 方法。但这个方法有不少坑:它默认执行的是浅拷贝,如果对象里有引用类型的字段,就必须手动在 clone 里再对这些字段做一次拷贝,否则两个对象会共享内部数据。另外 Cloneable 只是一个标记接口,不实现它就直接调用 clone,会抛出异常。
如果要做到深拷贝,常见有两条路。一条是继续用 clone,但每一层引用类型都要递归地复制一遍;这种方式写起来很啰嗦,而且一旦类里新增了字段,很容易忘记同步修改,留下隐患。另一条是通过序列化把对象写出去再读回来,得到一棵完全独立的对象树。它写起来简单,代价是性能开销更大,而且要求涉及的类都可序列化。
还有一个特别容易踩坑的地方,是集合的“复制”。比如用 new ArrayList 包住一个已有的列表,看起来像是复制了一份集合,但复制到的只是容器本身,里面装的元素依然是同一批对象的引用。改动其中一个元素对象,两个集合里看到的都会跟着变。所以判断一次拷贝到底是深还是浅,不能只看最外层的类型,而要看里面的元素是否也被重新创建了。
还有一种更朴素的做法,就是干脆自己写一个复制构造方法,或者在需要的地方显式地创建新对象并逐个设置字段。虽然看起来笨,但它把“复制了什么、哪些字段是共享的”这件事写得清清楚楚,反而最不容易出错。
实际项目里,我现在的判断标准是这样的:如果对象本身是不可变的,共享完全没问题,根本不需要拷贝;如果只是想把对象当成一份只读的快照,浅拷贝往往就够了;只有当对象内部那些可变的字段确实会被独立修改时,才需要真正的深拷贝。
这次梳理让我明白,拷贝这件事本质上是在问一个问题:哪些数据是可以共享的,哪些必须独立。想清楚这一点,比记住 clone 的写法要重要得多。

浙公网安备 33010602011771号