【面试精讲】深克隆和浅克隆的实现方式?深克隆和浅克隆有什么区别?
目录
在Java编程中,对象的克隆是创建对象副本的过程。这在需要独立修改原始对象和副本对象时非常有用。克隆分为两种类型:浅克隆(Shallow Clone)和深克隆(Deep Clone)。理解这两者的区别对于避免程序中的隐藏bug和内存泄漏至关重要。
浅克隆创建一个新对象,其字段值与原始对象相同。对于基本数据类型的字段,浅克隆会直接复制值。但对于引用数据类型,浅克隆并不创建被引用对象的副本,而是复制引用地址。因此,如果原始对象或克隆对象中的一个修改了引用类型的字段,这一变化也会反映在另一个对象上。
实现方式:实现Cloneable
接口并覆盖clone()
方法。
- // Person类包含了一个引用类型的字段Address。
- public class Person implements Cloneable {
- private String name;
- private int age;
- private Address address; // 引用类型
-
- public Person(String name, int age, Address address) {
- this.name = name;
- this.age = age;
- this.address = address;
- }
-
- @Override
- protected Object clone() throws CloneNotSupportedException {
- // 使用默认的clone()方法进行浅克隆时,Address对象不会被克隆,仅仅复制其引用。
- return super.clone();
- }
- }
深克隆不只是复制对象的直接字段,还包括对象内部所有层次的字段。对于引用数据类型,深克隆会递归克隆所有子对象,从而确保克隆对象与原始对象完全独立,修改一个对象的状态不会影响到另一个。
实现方式:
clone()
方法实现深克隆- public class Address implements Cloneable {
- private String city;
-
- public Address(String city) {
- this.city = city;
- }
-
- @Override
- protected Object clone() throws CloneNotSupportedException {
- return super.clone();
- }
- }
-
- public class Person implements Cloneable {
- private String name;
- private int age;
- private Address address;
-
- @Override
- protected Object clone() throws CloneNotSupportedException {
- Person cloned = (Person) super.clone();
- cloned.address = (Address) address.clone(); // 深克隆
- return cloned;
- }
- }
这种方法不需要对每个对象单独实现Cloneable
接口,但要求所有对象都必须是可序列化的(即实现Serializable
接口)。
- import java.io.*;
-
- public class DeepCopyUtil {
-
- @SuppressWarnings("unchecked")
- public static
T deepCopy(T object) { - try {
- ByteArrayOutputStream bos = new ByteArrayOutputStream();
- ObjectOutputStream oos = new ObjectOutputStream(bos);
- oos.writeObject(object);
- oos.flush();
-
- ByteArrayInputStream bin = new ByteArrayInputStream(bos.toByteArray());
- ObjectInputStream ois = new ObjectInputStream(bin);
-
- return (T) ois.readObject();
- } catch (IOException | ClassNotFoundException e) {
- throw new RuntimeException(e);
- }
- }
- }
使用 Apache Commons Lang 来实现深克隆
- import org.apache.commons.lang3.SerializationUtils;
- import java.io.Serializable;
-
- /**
- * 深克隆实现方式四:通过 apache.commons.lang 实现
- */
- public class FourthExample {
- public static void main(String[] args) throws CloneNotSupportedException {
- // 创建对象
- Address address = new Address(110, "北京");
- People p1 = new People(1, "Java", address);
- // 调用 apache.commons.lang 克隆对象
- People p2 = (People) SerializationUtils.clone(p1);
-
- // 修改原型对象
- p1.getAddress().setCity("西安");
- // 输出 p1 和 p2 地址信息
- System.out.println("p1:" + p1.getAddress().getCity() + " p2:" + p2.getAddress().getCity());
- }
-
- static class People implements Serializable {
- private Integer id;
- private String name;
- private Address address;
- }
-
- static class Address implements Serializable {
- private Integer id;
- private String city;
- }
- }
- // clone() 是使用 native 修饰的本地方法,因此执行的性能会很高,并且它返回的类型为 Object,因此在调用克隆之后要把对象强转为目标类型才行。
- protected native Object clone() throws CloneNotSupportedException;
对于所有对象来说,x.clone() !=x ,应当返回 true,因为克隆对象与原对象不是同一个对象;
对于所有对象来说,x.clone().getClass() == x.getClass() ,应当返回 true,因为克隆对象与原对象的类型是一样的;
对于所有对象来说,x.clone().equals(x) ,应当返回 true,因为使用 equals 比较时,它们的值都是相同的。
在修改克隆对象的第一个元素之后,原型对象的第一个元素也跟着被修改了,这说明 Arrays.copyOf() 其实是一个浅克隆。
- People[] o1 = {new People(1, "Java")};
- People[] o2 = Arrays.copyOf(o1, o1.length);
-
- // 修改原型对象的第一个元素的值
- o1[0].setName("Jdk");
-
- System.out.println("o1:" + o1[0].getName());
- System.out.println("o2:" + o2[0].getName());
浅克隆与深克隆解决的是Java对象复制的问题,它们之间的主要区别在于是否对对象内部的引用类型进行递归复制。选择哪种克隆方式取决于你的具体需求。对于需要完全独立的对象复制,深克隆是更好的选择。然而,深克隆的实现比浅克隆更复杂,且性能开销较大。正确理解和使用这两种克隆方法,对编写健壮的Java应用程序至关重要。
如果本文对你有帮助 欢迎 关注 、点赞 、收藏 、评论, 博主才有动力持续记录遇到的问题!!!
📫作者简介:嗨,大家好,我是 小明(小明Java问道之路),互联网大厂后端研发专家,2022博客之星TOP3 / 博客专家 / CSDN后端内容合伙人、InfoQ(极客时间)签约作者、阿里云签约博主、全网 6 万粉丝博主。
🍅 文末获取联系 🍅 👇🏻 精彩专栏推荐订阅收藏 👇🏻
专栏系列(点击解锁)
学习路线(点击解锁)
知识定位
全面讲解MySQL知识与企业级MySQL实战 🔥计算机底层原理🔥