JavaNotes_01:Clone:深克隆与浅克隆(深拷贝与浅拷贝)

Clone克隆


1. 简述

  • 克隆:使用clone()方法,可以快速的创建一个对象的副本,并且两个对象指向不同的内存地址,但克隆出来的对象的属性值是由被克隆对象初始化的。
  • 实现克隆的条件:类必须实现Cloneable接口才能被克隆,类中必须重写clone()方法
  • 异常Exception in thread "main" java.lang.CloneNotSupportedException 是因为在重写clone()方法类中没有实现Cloneable接口

2. 查看源码

  • Cloneable接口源代码:
/**
 * Cloneable此接口不包含clone()方法。 因此,只能通过实现该接口的事实来克隆对象是不可能的。
 * 所以要实现克隆还需重写Object类中的clone()方法
 */
public interface Cloneable {
}
  • clone()方法在Object类中的源代码:
/**
 * clone()方法是由native关键字修饰的可见克隆是调用底层的C++实现的,不用调用构造函数
 * 且native修饰的方法执行效率比非native修饰的高,故clone()方法是Java中实现复制对象最简单,最高效的手段
 * clone()方法由protected修饰,当一个类在重写clone()方法时,需要修改成public访问修饰符,
 * 方便其他类能够访问这个类的这个方法,避免出错
 */
protected native Object clone() throws CloneNotSupportedException;

3. New与 clone()的区别

  • new

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

  • clone()

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

  • 复制引用和复制对象的区别

    • 复制引用
    • Student实体类代码:
    package com.learn.beans;
    public class Student {
        // 学生属性 姓名
        private String name;
       
        /**
         * 有参构造方法,初始化操作(给姓名赋值)
         * @param name
         */
        public Student(String name) {
            this.name = name;
        }
    
        /**
         * 获取姓名
         * @return
         */
        public String getName() {
            return name;
        }
    
        /**
         * 设置姓名
         * @param name
         */
        public void setName(String name) {
            this.name = name;
        }
    }
    
    • StudentTest代码:
    package com.learn.test;
    
    import com.learn.beans.Student;
    
    public class StudentTest {
        public static void main(String[] args) {
            // 创建Student对象stu并初始化  name-->张三
            Student stu = new Student("张三");
            // 创建Student对象stuCopy并将stu赋给它
            Student stuCopy = stu;
    
            // 输出打印stu对象的引用地址
            System.out.println(stu);
            // 输出打印stuCopy对象的引用地址
            System.out.println(stuCopy);
    
            // 输出打印stu对象的name
            System.out.println("未做更改前stu-->name为:"+stu.getName());
            // 输出打印stuCopy对象的name
            System.out.println("未做更改前stuCopy-->name为:"+stuCopy.getName());
    
            // 更改stuCopy的姓名
            stuCopy.setName("李四");
    
            // 输出打印stu对象的name
            System.out.println("更改后stu-->name为:"+stu.getName());
            // 输出打印stuCopy对象的name
            System.out.println("更改后stuCopy-->name为:"+stuCopy.getName());
    
        }
    }
    
    
    • 结果:
    com.learn.beans.Student@1540e19d
    com.learn.beans.Student@1540e19d
    未做更改前stu-->name为:张三
    未做更改前stuCopy-->name为:张三
    更改后stu-->name为:李四
    更改后stuCopy-->name为:李四
    
    • 地址值是相同的,可以确定stu和stuCopy指向是同一个对象Student(“张三”)。内存中的情景如下图所示:
      在这里插入图片描述

    • 复制对象

    • Student实体类

    package com.learn.beans;
    
    public class Student implements Cloneable{
        // 学生属性 姓名
        private String name;
    
        /**
         * 有参构造方法,初始化操作(给姓名、年龄赋值)
         * @param name
         */
        public Student(String name) {
            this.name = name;
        }
    
        /**
         * 获取姓名
         * @return
         */
        public String getName() {
            return name;
        }
    
        /**
         * 设置姓名
         * @param name
         */
        public void setName(String name) {
            this.name = name;
        }
    
        /**
         * 重写clone方法
         * @return
         * @throws CloneNotSupportedException
         */
        @Override
        public Object clone() throws CloneNotSupportedException {
            return super.clone();
        }
    }
    
    
    • StudentTest测试类
    package com.learn.test;
    
    import com.learn.beans.Student;
    
    public class StudentTest {
        public static void main(String[] args) throws CloneNotSupportedException {
            // 创建Student对象stu并初始化  name-->张三
            Student stu = new Student("张三");
            // 调用clone()方法创建Student对象stuCopy
            Student stuCopy = (Student) stu.clone();
    
            // 输出打印stu对象的引用地址
            System.out.println(stu);
            // 输出打印stuCopy对象的引用地址
            System.out.println(stuCopy);
    
            // 输出打印stu对象的name
            System.out.println("未做更改前stu-->name为:"+stu.getName());
            // 输出打印stuCopy对象的name
            System.out.println("未做更改前stuCopy-->name为:"+stuCopy.getName());
    
            // 更改stuCopy的姓名
            stuCopy.setName("李四");
    
            // 输出打印stu对象的name
            System.out.println("更改后stu-->name为:"+stu.getName());
            // 输出打印stuCopy对象的name
            System.out.println("更改后stuCopy-->name为:"+stuCopy.getName());
    
        }
    }
    
    
    • 结果:
    com.learn.beans.Student@1540e19d
    com.learn.beans.Student@677327b6
    未做更改前stu-->name为:张三
    未做更改前stuCopy-->name为:张三
    更改后stu-->name为:张三
    更改后stuCopy-->name为:李四
    
    • 两个对象的地址是不同的,说明创建了新对象, 而不是把原对象的地址赋给了新的引用变量,内存中的情景如下图所示:
      在这里插入图片描述

4. 浅拷贝与深拷贝

  • 浅克隆shallow clone
    • 上面的复制对象就是浅克隆
    • 浅拷贝仔细分析图如下(上面的的图是为了直观)主要看堆里的Eden区,其实他们的name属性指向的是同一个String类型的对象 “张三”
    • 问题先说: 为什么更改stuCopy的name属性而stu的还是张三并且他们的地址也不同?
      因为String对象是不可更改的,更改就是重新分配内存重新创建,为什么“张三”还在,我认为是stu里的name还在引用着,而stuCopy更改它他要重新创建故地址和stu里的张三不一样,但他不是深克隆,因为String类没有实现Cloneable接口
      在这里插入图片描述
  • 深克隆deep clone
    • clone对象时将对象里的属性也要clone一份,这要求属性是复合(引用)类型。例如在克隆学生对象时,连同学生对象的书包也要克隆一份
    • 要求:克隆对象类和对象属性类都要实现Cloneable接口,类中都要重写clone方法
    • 准备:student学生实体类,Bag书包类,DeepCloneTest测试类
    • Bag.java
    package com.learn.beans;
    
    public class Bag implements Cloneable {
        private String brand; // 品牌
    
        public Bag(String brand) {
            this.brand = brand;
        }
    
        public String getBrand() {
            return brand;
        }
    
        public void setBrand(String brand) {
            this.brand = brand;
        }
    
        @Override
        public Object clone() throws CloneNotSupportedException {
            return super.clone();
        }
    }
    
    
    • Student.java
    package com.learn.beans;
    
    public class Student implements Cloneable{
        private String name; // 学生姓名
        private Bag bag;     // 包
    
        public Student(String name, Bag bag) {
            this.name = name;
            this.bag = bag;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public Bag getBag() {
            return bag;
        }
    
        public void setBag(Bag bag) {
            this.bag = bag;
        }
    
        /**
         * 重写clone方法
         * @return
         * @throws CloneNotSupportedException
         */
        @Override
        public Object clone() throws CloneNotSupportedException {
            Student stu = (Student) super.clone(); // 实现自身的克隆
            stu.bag  = (Bag) stu.bag.clone();      // 实现属性的克隆
            return stu; // 返回自身
        }
    }
    
    
    
    • DeepCloneTest.java
    package com.learn.test;
    
    import com.learn.beans.Bag;
    import com.learn.beans.Student;
    
    public class DeepCloneTest {
        public static void main(String[] args) throws CloneNotSupportedException {
            // 创建Student对象stu并初始化  name-->张三
            Student stu = new Student("张三",new Bag("耐克"));
            // 调用clone()方法创建Student对象stuCopy
            Student stuCopy = (Student) stu.clone();
            // 输出打印stu对象的引用地址
            System.out.println("stu------地址:" + stu);
            // 输出打印stuCopy对象的引用地址
            System.out.println("stuCopy--地址:" + stuCopy);
            // stu bag的地址
            System.out.println("stu-----bag--地址:" + stu.getBag());
            // stuCopy bag的地址
            System.out.println("stuCopy-bag--地址:" + stuCopy.getBag());
            System.out.println("更改前:");
            System.out.println("stu--bag:" + stu.getBag().getBrand() + "\tstuCopy--bag:" + stuCopy.getBag().getBrand());
            stuCopy.getBag().setBrand("森马");
            System.out.println("更改后:");
            System.out.println("stu--bag:" + stu.getBag().getBrand() + "\tstuCopy--bag:" + stuCopy.getBag().getBrand());
        }
    }
    
    
    • 运行结果
    stu------地址:com.learn.beans.Student@1540e19d
    stuCopy--地址:com.learn.beans.Student@677327b6
    stu-----bag--地址:com.learn.beans.Bag@14ae5a5
    stuCopy-bag--地址:com.learn.beans.Bag@7f31245a
    更改前:
    stu--bag:耐克	stuCopy--bag:耐克
    更改后:
    stu--bag:耐克	stuCopy--bag:森马
    
    • 有结果可以看出stu–>stuCopy实现了对象拷贝,且bag属性也实现了属性对象的拷贝,内存如图:
      在这里插入图片描述

5. 总结

  • clone的实现实现需要类实现Cloneable接口,类中重写clone方法
  • 复制引用和复制对象的区别在于复制后两个对象是否指向同一地址,同则为复制引用,反之复制对象
  • 浅拷贝与深拷贝的区别在于复合(引用)属性对象是否拷贝,不拷贝则为浅拷贝,反之为深拷贝
  • 注意:String类型的对象是不可更改的,更改即为重新创建,且其并未实现Cloneable接口,切莫与深拷贝混淆
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值