Java中Object.clone()默认浅拷贝,手动深拷贝需用IdentityHashMap缓存已克隆对象以避免循环引用导致的StackOverflowError;应提取deepCopy方法并传入cache,字段复制前查缓存,基本类型和不可变对象直接赋值;也可改用JSON序列化、Kryo或SerializationUtils等支持循环引用的方案;架构上应减少循环依赖,用ID、弱引用或中间层解耦。

Java 中 Object.clone() 默认是浅拷贝,若手动实现深拷贝时未处理循环引用(如 A → B → A),递归复制会无限深入,最终触发 StackOverflowError。核心解法是引入“已拷贝对象缓存”,在递归前查重,避免重复克隆同一对象。
用 IdentityHashMap 缓存已克隆对象
这是最常用且高效的方式:利用对象引用的唯一性(而非 equals),记录原始对象到克隆对象的映射,遇到已处理对象直接返回其克隆体,切断递归链。
- 在 clone 方法开头初始化
IdentityHashMap<object object></object>(注意传参或作为方法参数传递) - 每次进入 clone 逻辑前,先查 map:若存在对应克隆体,直接返回,不继续递归
- 若不存在,新建克隆对象,put 进 map,再递归处理其字段
- 适用于自定义类的手动深拷贝,也兼容序列化/反序列化等其他深拷贝路径
避免在 clone 中直接调用 this.clone()
很多开发者误以为重写 clone() 就该调用 super.clone() 再赋值字段,但若字段本身又调用 clone()(尤其互相引用),极易陷入死递归。应明确区分“克隆入口”和“字段复制逻辑”。
- 把深拷贝逻辑提取为独立方法(如
deepCopy(IdentityHashMap cache)),clone()只负责创建新实例 + 调用该方法 - 字段复制时,对每个引用类型字段,先判断是否为空、是否已在 cache 中,再决定是否递归调用 deepCopy
- 基本类型、不可变对象(String、Integer 等)无需缓存,直接赋值即可
改用非递归方式或第三方库
若手动维护缓存较复杂,可考虑更健壮的替代方案:
当用户要求为 Java 代码添加注释、修改注释、审查注释、整理注释格式,或要求按照代码注释规范处理 Java 文件时,使用本技能。本技能用于统一生成和维护 Java 类注释模板、按 Git 对比 release 判断版本号、补充每行代码上方中文块注释、调整注释空行规则,并保持 Java 文件 UTF-8 无 BOM 编码。典型触发场景包括:补充类注释、更新 @version、检查 @author 来源、整理行尾注释、把注释放到代码上一行、检查注释是否符合规范、构建前检查 Java 注释风格。
立即学习“Java免费学习笔记(深入)”;
- JSON 序列化/反序列化(如 Jackson、Gson):自动跳过循环引用(默认报错,但可配置 `@JsonIdentityInfo` 或 `SimpleModule` 处理)
- Kryo:高性能二进制序列化框架,内置循环引用支持(需启用 `setReferences(true)`)
- Apache Commons Lang 的 SerializationUtils.clone():基于 Java 原生序列化,能天然处理循环引用(前提是所有类都可序列化)
设计阶段就规避深层循环依赖
技术手段是补救,架构层面减少循环引用才是根本:
- 用 ID 或弱引用(WeakReference)代替强引用闭环,例如 A 持有 B 的 id,B 持有 A 的 id,运行时按需查找
- 引入中间层(如 Context、Registry)解耦双向关联,避免对象间直接循环持有
- 在领域模型中明确“主从关系”,只允许单向引用,辅以查询接口获取反向关联
不复杂但容易忽略。关键不是禁止 clone,而是让 clone 知道“谁已经来过了”。

















