Java深克隆不能直接用clone()因Object.clone()是浅拷贝,集合等引用类型未复制,导致状态共享;需实现Cloneable、递归克隆或用SerializationUtils.clone()、JSON转换、自定义工具类等方案。

Java深克隆为什么不能直接用clone()方法
因为Object.clone()默认是浅拷贝,集合字段(比如ArrayList、HashMap)里的元素引用不会被复制,新旧对象仍共享同一份集合内容。改一个,另一个跟着变——这在状态隔离、缓存快照、多线程临时副本等场景下就是bug根源。
常见错误现象:original.getList().add("x")之后,cloned.getList()里也出现了"x";或者序列化前修改了集合,反序列化后发现原始对象也被污染。
- 必须确保目标类实现
Cloneable接口(否则抛CloneNotSupportedException) - 所有嵌套的集合、自定义对象字段,都得手动递归调用
clone()或新建实例 - 如果用了Lombok的
@Data或@Builder,它默认不处理深克隆逻辑,@AllArgsConstructor也不等于能安全复制集合
用SerializationUtils.clone()做标准深克隆
Apache Commons Lang的SerializationUtils.clone()是最省心的通用方案:把对象序列化成字节数组再反序列化,天然绕过引用共享问题。但前提是所有字段类型都可序列化(Serializable),且构造函数无副作用。
使用场景:实体类结构稳定、不含不可序列化资源(如Thread、Socket、InputStream)、对性能不敏感的配置快照或测试数据准备。
立即学习“Java免费学习笔记(深入)”;
- 添加Maven依赖:
org.apache.commons:commons-lang3:3.12.0 - 目标类及其所有成员变量类型(包括集合泛型)都必须实现
Serializable - 避免在
readObject()里做复杂初始化,否则反序列化时可能触发意外逻辑 - 注意:静态字段和
transient字段不会被克隆,这是预期行为,不是bug
示例:
Person original = new Person("Alice", Arrays.asList("Java", "Python"));
Person cloned = SerializationUtils.clone(original); // 集合内容完全独立
JSON转换法适合“数据类”但有隐性损耗
用ObjectMapper(Jackson)或Gson先转JSON字符串再反解析,本质是重建对象,也能达成深克隆效果。但它只保留可序列化的字段值,会丢失类型信息、空集合/数组的原始类型、以及任何非JSON友好结构(如LocalDateTime需额外配置序列化器)。
常见错误现象:new ArrayList<String>()克隆后变成Arrays$ArrayList(Jackson默认返回不可变列表);BigDecimal精度丢失;枚举被转成字符串后反解析失败。
- 务必显式配置
ObjectMapper:启用DeserializationFeature.USE_JAVA_ARRAY_FOR_JSON_ARRAY、注册JavaTimeModule等 - 泛型集合反序列化必须用
TypeReference,不能只写clazz,否则运行时类型擦除导致集合元素变成LinkedHashMap - 性能比序列化方案更差:涉及字符串编解码、GC压力大,不适合高频调用
示例:
ObjectMapper mapper = new ObjectMapper(); mapper.registerModule(new JavaTimeModule()); Person cloned = mapper.readValue(mapper.writeValueAsString(original), Person.class);
自定义工具类要小心泛型擦除和循环引用
手写深克隆工具类看似可控,实际极易翻车。最大陷阱是Java泛型在运行时不存在,List<User>和List<Order>在反射中都是List,无法自动推导元素类型去克隆;更麻烦的是对象图里存在循环引用(A→B→A),不加检测会栈溢出。
使用场景:已有成熟反射/注解框架(如Spring的BeanUtils.copyProperties),且能接受部分字段忽略或按规则跳过。
- 不要依赖
getClass().getDeclaredFields()暴力遍历——final字段、父类字段、泛型集合元素都会漏掉 - 用
Field.getGenericType()配合ParameterizedType解析泛型,再递归克隆每个元素 - 必须维护一个
IdentityHashMap<Object, Object>记录已克隆对象,遇到重复引用直接返回缓存结果 - 优先考虑用现成库(如Kryo、FST)替代手写,它们已解决上述问题
真正容易被忽略的是:集合字段本身可能是null,而很多工具类默认跳过null值,导致克隆后集合为null而非空集合——业务代码若没判空,立刻NullPointerException。










