一篇搞懂深克隆与浅克隆!!(面试相关)

为什么要使用克隆(clone)?

当拷贝一个变量时,原始变量与拷贝变量引用同一个对象,这就是说,改变一个变量所引用的对象将会对另外一个变量产生影响,这种情况下就可以使用就可以使用克隆。举个栗子:

学生类:

@Accessors(chain = true)
@NoArgsConstructor
@AllArgsConstructor
@Data
public class Student {
    
    
    int age ;
    String name;
}

测试:

 Student studentA = new Student(18,"A");
        System.out.println(studentA.toString());
        Student studentB = studentA;
        studentB.setName("B");
        System.out.println(studentB.toString());
        System.out.println(studentA.toString());

输出结果:
在这里插入图片描述
可以看出将studentA赋值给studentB并将其的名字改为B而A的名字却也发生了改变。那么此时的两者内存地址关系呢?讓我們打印一下:

System.out.println(System.identityHashCode(studentA));
System.out.println(System.identityHashCode(studentB)); 

結果:
在这里插入图片描述

可以看出两个对象的内存地址都是一样的。说明studentB和studentA都只是一个相同引用,而这种情况下的拷贝只是引用的复制
那么使用了克隆对象的情况呢?此时我们对例子做个简单变化。实现克隆后的学生类:

@Accessors(chain = true)
@NoArgsConstructor
@AllArgsConstructor
@Data
public class Student implements Cloneable{
    
    
    int age ;
    String name;

    @Override
    protected Student clone() throws CloneNotSupportedException {
    
    
        return (Student) super.clone();
    }
}

测试:

    @Test
    public void testClone(){
    
    
        Student studentA = new Student(18,"A");
        System.out.println(studentA);
        Student studentB = null;
        try {
    
    
            studentB = studentA.clone();
        } catch (CloneNotSupportedException e) {
    
    
            e.printStackTrace();
        }
        studentB.setName("B");
        System.out.println(studentB);
        System.out.println(studentA);

    }

输出结果:
在这里插入图片描述
此时就可以看到此时studentA就没有改变。打印一下内存地址:
在这里插入图片描述
可以看出克隆后的对象引用指向的地址已经不是原来的对象。画个引用图更好理解:
在这里插入图片描述
可以看出克隆的对象是开辟出了一个新的内存空间。那么就会有人就会问:那么这样的方式和new一个对象又有什么差别呢?而这也是很多面试会考的问题之一。

clone一个对象和new一个对象的差别

先来大体了解下目前创建对象的几种方式:

  • new 关键字
    new 操作符的本意是分配内存。程序执行到 new 操作符时,首先去看 new 操作符后面的类型,因为知道了类型,才能知道要分配多大的内存空间。分配完内存之后,再调用构造函数,填充对象的各个域,这一步叫做对象的初始化,构造方法返回后,一个对象创建完毕,可以把他的引用(地址)发布到外部,在外部就可以使用这个引用操纵这个对象。

  • 反射机制
    这种方式也是通过反射方法,调用构造器来初始化对象。

  • 使用clone方法
    clone 在第一步是和 new 相似的,都是分配内存,调用 clone 方法时,分配的内存和原对象(即调用 clone 方法的对象)相同,然后再使用原对象中对应的各个域,填充新对象的域,填充完成之后,clone 方法返回,一个新的相同的对象被创建,同样可以把这个新对象的引用发布到外部。

  • 反序列化
    序列化反序列化方式是从文件中还原类的对象,所以不会调用构造函数。

  • Unsafe.allocateInstance()
    此方法为 native 方法,没有初始化实例字段。它只做了分配内存空间,返回内存地址,并没有调用构造函数。所以 Unsafe.allocateInstance() 方法创建的对象都是只有初始值,没有默认值也没有构造函数设置的值,因为它完全没有使用 new 机制,直接操作内存创建了对象。

本篇主要涉及new和clone的方式,因此其余三种就大体提一下略过。
由此可以看出其主要的区别:

  • 在java中clone()与new都能创建对象。
  • clone()不会调用构造方法;new会调用构造方法。其域为原有对象的各个域的填充
    结论:由于clone是直接的复制,而new则要调用构造方法,创建对象时要赋值初始化所以在复杂对象的创建上clone的效率会高于new一些。
    注意:结论上指的是复杂对象。由于编译器会对new操作符进行一些优化,所以new创建简单对象的时间有时会更少,对刚刚的学生类再举个栗子:
	@Test
    public void testTime() throws CloneNotSupportedException {
    
    
        long s1 = System.currentTimeMillis();
        for (int i = 0; i < 1000000; i++) {
    
    
            Student student = new Student();
        }
        long s2 = System.currentTimeMillis();
        System.out.println("new 方式耗时:"+(s2-s1));

        long s3 = System.currentTimeMillis();
        Student s = new Student();
        for (int i = 0; i < 1000000; i++) {
    
    
            Student student = s.clone();
        }
        long s4 = System.currentTimeMillis();
        System.out.println("clone 方式耗时:"+(s4-s3));

    }

结果:
在这里插入图片描述

可以看出在这种对简单对象的赋值上直接选择new或许是个不错的选择。
了解了这些后再来看看如何使用克隆

克隆的方式

浅克隆(ShallowClone)

浅克隆是指拷贝对象时仅仅拷贝对象本身(包括对象中的基本变量),而不拷贝对象包含的引用指向的对象。

对之前的学生类进行改造下,使其包含一个引用对象。

    @Accessors(chain = true)
    @NoArgsConstructor
    @AllArgsConstructor
    @Data
    public class Student implements Cloneable{
    
    
        int age ;
        String name;
        Teacher teacher;
        @Override
        protected Student clone() throws CloneNotSupportedException {
    
    
            return (Student) super.clone();
        }
    }

    @Accessors(chain = true)
    @NoArgsConstructor
    @AllArgsConstructor
    @Data
    public class Teacher{
    
    
        int age ;
        String name;
    }

测试类:

 @Test
    public void testClone(){
    
    
        Student studentA = new Student(18,"A",new Teacher(28,"陈老师"));
        Student studentB = null;
        try {
    
    
            studentB = studentA.clone();
        } catch (CloneNotSupportedException e) {
    
    
            e.printStackTrace();
        }
        studentB.getTeacher().setAge(29);
        System.out.println(studentB.getTeacher());
        System.out.println(studentA.getTeacher());
    }

输出:
在这里插入图片描述
可以看出此时即使的是使用了clone方法。但是改变其引用时,还是把原始的引用给修改了。让我们分别打印一下此时的两个student中teacher的内存地址:
在这里插入图片描述
可以得出两个student对象中的对象引用其实是指的同一个teacher对象,本质还是复制的引用。
画个引用图如下:
在这里插入图片描述
由此可见这种方式只复制基本类型的数据,引用类型的数据只复制了引用的地址,引用的对象并没有复制,在新的对象中修改引用类型的数据会影响原对象中的引用,而这种克隆的方式也叫做浅克隆
而为了解决浅克隆的弊端,而有了深克隆

深克隆(DeepClone)

深克隆:
是在引用类型的类中也实现了clone,是clone的嵌套,复制后的对象与原对象之间完全不会影响。因此将上述例子的学生和教师类再改一下实现深克隆。

    @Accessors(chain = true)
    @NoArgsConstructor
    @AllArgsConstructor
    @Data
    public class Student implements Cloneable{
    
    
        int age ;
        String name;
        Teacher teacher;
        @Override
        protected Student clone() throws CloneNotSupportedException {
    
    
            Student student = (Student) super.clone();
            student.setTeacher( student.getTeacher().clone());
            return student;
        }
    }

    @Accessors(chain = true)
    @NoArgsConstructor
    @AllArgsConstructor
    @Data
    public class Teacher implements Cloneable{
    
    
        int age ;
        String name;
        @Override
        protected Teacher clone() throws CloneNotSupportedException {
    
    
           return (Teacher) super.clone();
        }
    }

再按刚刚的测试代码再输出一遍:
在这里插入图片描述
可以看出此时的年龄已经修改成功,内存地址也不再相同。此时的引用关系应为:
在这里插入图片描述
(ps:使用序列化也能完成深克隆的功能:对象序列化后写入流中,此时也就不存在引用什么的概念了,再从流中读取,生成新的对象,新对象和原对象之间也是完全互不影响的。但是序列里的引用对象也要都实现Serializable接口)

进一步可以想到如果teacher里面还有个subject的引用对象呢?
其实很简单,这种只是相当于再嵌套一层。也就是说,要实现student的深克隆,就要实现teacher的深克隆,最终是subject中实现一层浅克隆
所以可以认为使用clone实现的深克隆其实是浅克隆中嵌套了浅克隆

本篇到此为止了,就结束了如果您看到了最后,不妨收藏、点赞、评论一下吧!!!
持续更新,您的三连就是我最大的动力,虚心接受大佬们的批评和指点,共勉!
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_45938441/article/details/119948743