⭐ 推荐:社区规则条款 V1.0

Java 中如何实现对象的深拷贝和浅拷贝?

域名注册
域名注册 版主归队站长新人站长 👑年卡会员
发布于 2026-10-14 05:39 ·0 浏览 ·3 回复
内容摘要

Java 默认 Object.clone 仅实现浅拷贝,深拷贝需自行实现;浅拷贝引用字段共享,深拷贝递归复制对象图使修改副本不影响原对象,可用逐层 clone、序列化、拷贝构造或 JSON 转换,BeanUtils.copyProperti

Java 中如何实现对象的深拷贝和浅拷贝?,本文转载自 GJ站长论坛,原文地址:https://www.gj0.com/thread-1938.html
转载请注明出处,版权归原作者所有。
—/ 5暂无评分
还没有评分,来打第一分吧

全部回复 3

juming
juming 归队站长新人站长 1楼 2026-10-14 06:04

深拷贝优先用拷贝构造,其次 JSON 序列化,逐层 clone() 只适合对象图简单、所有类都能改的场景。

把楼主截断那段补完:User.clone() 里光调 super.clone() 不够,还要显式 copy.address = address.clone(),也就是每一层引用类型都得实现 Cloneable 并重写 clone()。这条链很脆——链上任何一个类漏实现、字段是 final(final 字段无法重新赋值),整条链就断了,所以更推荐拷贝构造:new User(src),字段逐个显式赋值,无反射无序列化,性能和可调试性都最好,代价是加字段时要同步改。

序列化分两种。JDK 序列化用 ObjectOutputStream 写进 ByteArrayOutputStream 再读回来,要求整条对象图都 implements Serializable,好处是加减字段不用改代码,坏处是慢、不可序列化的字段得标 transient 跳过。JSON 方式(Jackson)最省事,一行 mapper.readValue(mapper.writeValueAsString(src), User.class),但有两个硬坑:循环引用直接 StackOverflow;LocalDateTime、枚举、泛型集合容易丢类型,得注册 JavaTimeModule 或用 TypeReference。

BeanUtils.copyProperties,不管是 Spring 的还是 Apache 的,都只是浅拷贝,只复制属性引用,别指望它。

坑提醒:深拷贝不等于全量递归,String、Integer、LocalDate、枚举这些不可变对象直接共享引用就行,盲目深挖纯属浪费;单例也不该被复制出新实例。性能上拷贝构造通常比 JSON 快一个数量级,比 JDK 序列化快更多,热点路径别用序列化。

liulian
liulian 归队站长新人站长 #1469 2楼 2026-10-14 06:21
juming:深拷贝优先用拷贝构造,其次 JSON 序列化,逐层 `clone()` 只适合对象图简单、所有类都能改的场景。 把楼主截断那段补完:`User.clone()…

结论:这套梳理基本就是标准答案,我只补几个实战里最容易翻车的细节。

拷贝构造不是"字段逐个赋值"就完了,集合字段必须换一层容器:this.roles = new ArrayList<>(src.roles),否则两个对象共用同一个 list,add/remove 互相影响,而你只换了元素引用;元素本身可变的话还要再 map 一层拷贝构造。也别拿 Collections.unmodifiableList 当保护,它只是个视图,底层 list 照样共享。

Spring 和 Apache 两个 BeanUtils 参数顺序是反的:Spring 是 (source, target),Apache 是 (dest, orig),写反了不报错但目标对象是空的,这比"它只是浅拷贝"还阴。

JSON 那边再补两坑:Lombok 对象缺无参构造会让 Jackson 反序列化直接失败;泛型集合必须走 TypeReference<List<User>>,用 List.class 元素会悄悄变成 LinkedHashMap,报错要等到 ClassCastException 才现形。

选型上我的习惯:领域对象用拷贝构造,字段频繁变动或三方对象用 JSON,跨进程/缓存再上 JDK 序列化;热点路径别碰序列化,慢一到两个数量级是常态。

延伸建议:给每个可变引用字段写一条"改副本不影响原对象"的断言。否则以后有人加了新字段忘了同步,深拷贝会悄无声息退化成浅拷贝,而且测试还全绿。

满月居于空
满月居于空 版主资深会员新人站长 👑年卡会员 #1472 3楼 2026-10-14 06:42
liulian:结论:这套梳理基本就是标准答案,我只补几个实战里最容易翻车的细节。 拷贝构造不是"字段逐个赋值"就完了,集合字段必须换一层容器:`this.roles = n…

这几条都踩在点上了,尤其是"深拷贝悄悄退化"那个断言,我自己吃过一次亏之后就一直留着。再补三个容易漏的点。

集合字段换容器时,Java 10+ 可以直接用 List.copyOf(src.roles),它确实会新建容器,和 Collections.unmodifiableList 那种纯视图不是一回事;但它不接受 null 元素,且元素引用照样共享,真要元素也独立还得流式 map 一遍。另外对不可变集合它会直接返回原对象,这是优化不是 bug。

选型上还有个被忽略的依据是循环引用和共享引用。JDK 序列化内部维护引用表,对象图有环也能正常还原,而且同一个对象被两个字段引用时,副本里依然指向同一个副本,身份关系是保住的;JSON 一遇环就无限递归或直接 StackOverflowError,遇共享引用则展开成两份,语义已经变了。所以对象图有环时,序列化反而比 JSON 安全,代价就是你说的那个数量级。

Jackson 缺无参构造那坑,除了 @JsonCreator + @JsonProperty,@Builder 加 @Jacksonized 也能解,record 在 2.12+ 原生支持。走 JDK 序列化做缓存的话,别忘了 serialVersionUID,类一改不一致就是 InvalidClassException。

那条断言我一般拆成两步:改副本的引用字段、再改副本集合里的元素,浅拷贝只在第二步露馅。