如何理解clone对象?

1  为什么要用 clone ?
在实际编程过程中,我们常常要遇到这种情况:有一个对象 A,在某一时刻 A 中已经包含
了一些有效值,此时可能会需要一个和 A 完全相同新对象 B,并且此后对 B 任何改动都不
会影响到 A 中的值,也就是说,A 与 B 是两个独立的对象,但 B 的初始值是由 A 对象确定
的。在 Java 语言中,用简单的赋值语句是不能满足这种需求的。要满足这种需求虽然有很
多途径,但实现 clone()方法是其中最简单,也是最高效的手段。
2 new  一个对象的过程和 clone  一个对象的过程区别
new 操作符的本意是分配内存。程序执行到 new 操作符时,首先去看 new 操作符后面的
类型,因为知道了类型,才能知道要分配多大的内存空间。分配完内存之后,再调用构造函
数,填充对象的各个域,这一步叫做对象的初始化,构造方法返回后,一个对象创建完毕,
可以把他的引用(地址)发布到外部,在外部就可以使用这个引用操纵这个对象。
clone 在第一步是和 new 相似的,都是分配内存,调用 clone 方法时,分配的内存和原对
象(即调用 clone 方法的对象)相同,然后再使用原对象中对应的各个域,填充新对象的
域,填充完成之后,clone 方法返回,一个新的相同的对象被创建,同样可以把这个新对象
的引用发布到外部。
3 clone 对象的使用
3.1 复制对象和复制引用的区别
复制对象复制的是对象本身,复制出来的对象在内存中和原来的对象不是同一个,也是不
同的引用地址。
复制引用就是给原来的对象在创建一个引用,原来的引用和复制出来的引用都是指向同一
 

<think>我们正在讨论如何正确实现Java对象的深拷贝(deep clone)。根据用户提供的引用和之前关于Object类源码的分析,我们知道Object.clone()方法默认是浅拷贝。要实现深拷贝,需要递归地复制对象中的所有引用类型成员。 引用[1]和[2]都指出了深拷贝与浅拷贝的区别:浅拷贝只复制基本数据类型和引用(即指向同一个对象),而深拷贝会复制所有引用类型成员所指向的对象,从而创建一个完全独立的副本。 引用[3]则提供了一些深拷贝的实现方案,包括使用序列化(如JSON序列化)和工具类。 下面我们将详细说明如何正确实现深拷贝: 方法一:重写clone方法(递归调用clone) 1. 让类实现Cloneable接口(标记接口)。 2. 重写clone()方法,将访问修饰符从protected改为public。 3. 在clone方法中,先调用super.clone()得到浅拷贝对象。 4. 对于每个引用类型的成员变量,调用其clone()方法(如果该成员变量也支持深拷贝)或者通过其他方式创建新对象。 5. 注意:如果成员变量是数组,可以使用数组的clone方法(数组本身实现了Cloneable)或者手动创建新数组并复制每个元素。 方法二:使用拷贝构造方法(Copy Constructor)或拷贝工厂 1. 在类中定义一个构造方法,参数为该类的一个对象。 2. 在构造方法中,为每个成员变量创建新的对象(对于引用类型,递归调用其拷贝构造方法或clone方法)。 方法三:使用序列化(Serialization) 1. 将对象序列化为字节流,然后再反序列化成一个新的对象。这样得到的新对象与原对象完全独立。 2. 要求对象及其所有成员变量都必须实现Serializable接口。 3. 可以使用不同的序列化工具,如Java原生序列化、JSON序列化(如Jackson)等。 引用[3]中特别提到了使用Jackson进行JSON序列化实现深拷贝,并给出了三种策略(下中上策): - 下策:对象与JSON字符串互相转换(性能较低,因为字符串操作开销大) - 中策:对象与JsonNode互相转换(避免字符串操作,直接操作树结构) - 上策:对象与TokenBuffer互相转换(更高效,流式处理) 下面我们分别给出示例: 示例1:重写clone方法实现深拷贝 ```java public class Person implements Cloneable { private String name; // String是不可变对象,可以不用深拷贝(但为了演示,我们当作需要深拷贝) private Address address; // 引用类型 // 省略构造方法、getter/setter @Override public Person clone() throws CloneNotSupportedException { Person cloned = (Person) super.clone(); // 对引用类型成员进行深拷贝 cloned.address = this.address.clone(); // 这里假设Address也实现了Cloneable并重写了clone方法 return cloned; } } public class Address implements Cloneable { private String city; @Override public Address clone() throws CloneNotSupportedException { return (Address) super.clone(); } } ``` 示例2:使用拷贝构造方法 ```java public class Person { private String name; private Address address; public Person(Person other) { this.name = other.name; // String不可变,直接赋值 this.address = new Address(other.address); // 使用Address的拷贝构造方法 } } public class Address { private String city; public Address(Address other) { this.city = other.city; } } ``` 示例3:使用Jackson序列化实现深拷贝(上策:TokenBuffer) ```java import com.fasterxml.jackson.databind.*; public class DeepCopyUtil { private static final ObjectMapper objectMapper = new ObjectMapper(); public static <T> T deepCopy(T object) throws IOException { // 使用TokenBuffer作为中间人 TokenBuffer tokenBuffer = new TokenBuffer(objectMapper, false); objectMapper.writeValue(tokenBuffer, object); JsonParser parser = tokenBuffer.asParser(); return objectMapper.readValue(parser, (Class<T>) object.getClass()); } } ``` 注意:使用序列化方法时,需要确保类有无参构造方法(如果使用Jackson默认配置),并且所有字段都可以被序列化(没有transient和非public字段等限制)。 总结: 1. 如果对象结构简单且层次固定,重写clone方法是最直接的方式。 2. 如果对象结构复杂,或者不想依赖Cloneable接口,可以使用拷贝构造方法。 3. 如果对象结构非常复杂,且需要避免手动编写深拷贝代码,使用序列化(特别是Jackson的TokenBuffer)是高效且安全的选择。 最后,引用[3]特别提醒不要使用Spring或Apache Commons BeanUtils的copy方法,因为这些工具类实现的是浅拷贝。 因此,正确实现深拷贝需要根据具体场景选择合适的方法,并确保所有引用类型成员都被正确复制。 引用说明:[^1][^2][^3]</think>### Java 对象深拷贝(Deep Clone)实现指南 在 Java 中实现对象的深拷贝需要完全复制对象及其所有嵌套对象,确保原始对象与副本完全独立。以下是三种主流实现方案: --- #### 方法一:重写 `clone()` 方法(基础方案) ```java class Address implements Cloneable { String city; @Override protected Address clone() throws CloneNotSupportedException { return (Address) super.clone(); // 浅拷贝 } } class User implements Cloneable { String name; Address address; @Override public User clone() throws CloneNotSupportedException { User cloned = (User) super.clone(); cloned.address = this.address.clone(); // 关键:手动深拷贝引用类型 return cloned; } } // 使用示例 User original = new User("Tom", new Address("New York")); User deepCopy = original.clone(); ``` **关键点**: 1. 实现 `Cloneable` 标记接口 2. 重写 `clone()` 方法(提升为 `public`) 3. **递归调用**所有引用类型成员的 `clone()` 方法[^1] 4. 数组类型需特殊处理:`cloned.array = original.array.clone()` **局限**: - 需要修改类源码 - 嵌套层级深时代码冗长 - 循环引用会导致栈溢出 --- #### 方法二:序列化方案(推荐方案) ```java import com.fasterxml.jackson.databind.ObjectMapper; class DeepCopyUtil { private static final ObjectMapper objectMapper = new ObjectMapper(); // Jackson 序列化实现深拷贝 public static <T> T deepCopy(T obj) throws IOException { // 上策:TokenBuffer 流式处理(高效) TokenBuffer buffer = new TokenBuffer(objectMapper, false); objectMapper.writeValue(buffer, obj); return objectMapper.readValue(buffer.asParser(), (Class<T>) obj.getClass()); // 中策:JsonNode 转换 // JsonNode node = objectMapper.valueToTree(obj); // return objectMapper.treeToValue(node, obj.getClass()); // 下策:JSON 字符串中转(性能最低) // String json = objectMapper.writeValueAsString(obj); // return objectMapper.readValue(json, obj.getClass()); } } // 使用示例 User original = new User("Tom", new Address("New York")); User deepCopy = DeepCopyUtil.deepCopy(original); ``` **优势**: 1. **无需修改类源码**(只需实现 `Serializable`) 2. 自动处理复杂嵌套和循环引用 3. Jackson 的 `TokenBuffer` 方案性能最优(避免字符串转换)[^3] 4. 支持泛型对象 **注意**: - 使用 Jackson 需添加依赖:`com.fasterxml.jackson.core:jackson-databind` - 避免使用 Gson/Fastjson(类型擦除和精度问题)[^3] - 类需有无参构造器 --- #### 方法三:拷贝构造器/工厂方案 ```java class Address { String city; // 拷贝构造器 Address(Address other) { this.city = other.city; } } class User { String name; Address address; // 深拷贝工厂方法 static User deepCopy(User other) { User copy = new User(); copy.name = other.name; copy.address = new Address(other.address); // 创建新对象 return copy; } } ``` **适用场景**: - 不可变对象(如 String、Integer) - 简单数据结构 - 需要精细控制拷贝过程时 --- #### 方案对比 | 特性 | `clone()` 重写 | 序列化方案 | 拷贝构造器 | |--------------------|-----------------------|----------------------|------------------| | 修改源码需求 | 需要 | **不需要** | 需要 | | 循环引用处理 | 不支持 | **支持** | 不支持 | | 性能 | 高 | 中(Jackson 优化后) | 高 | | 泛型支持 | 有限 | **完善** | 有限 | | 第三方依赖 | 无 | Jackson | 无 | > **终极建议**:生产环境优先选择 **Jackson TokenBuffer 序列化方案**(上策),兼顾性能与可靠性[^3]。关键服务建议补充单元测试验证深拷贝效果。 --- ### 深拷贝验证方法 ```java // 验证深拷贝成功 assert deepCopy != original; // 不同对象 assert deepCopy.address != original.address; // 嵌套对象地址不同 deepCopy.address.setCity("London"); // 修改副本 assert "New York".equals(original.address.getCity()); // 原对象不变 ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值