窥探 引用拷贝、浅拷贝、深拷贝 的那些事 (clone版)

谁家玉笛暗飞声

散入春风满洛城


往期回顾
内部类


目录

引用拷贝

介绍

总结

浅拷贝

介绍

浅拷贝的步骤

深拷贝

介绍

引用拷贝

介绍

引用拷贝就是我们常用的 “赋值” ,只是复制了原对象的引用,即两个对象指向同一块内存堆地址。修改其中的一个对象会影响到另一个对象

例子 ~

当我们想要复制一个对象时,最自然的操作就是将该对象直接赋值给另一个对象

如下图:Student s2 = s1

class Student{
    private String name;
    private int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public Student() {
        this.name = "张三";
        this.age = 18;
    }

    public void StudentPrint(){
        System.out.println("name:"+name+",age:"+age);
    }
}

class Test1 {
    public static void main(String[] args) {
        Student s1 = new Student("李四",20);
        Student s2 = s1;
        s1.StudentPrint();
        s2.StudentPrint();
        System.out.println("========");
        System.out.println(s1);
        System.out.println(s2);
    }
}

输出的结果为:

name:李四,age:20
name:李四,age:20
========
Student@3d494fbf
Student@3d494fbf

根据打印结果我们可以知道:引用拷贝会生成一个新的对象引用地址,但是两个最终指向依然是同一个对象

 

总结

引用拷贝的优点在于能够快速的实现 “拷贝” (一个变量拥有另一个变量的所有属性)。缺点也很明显,由于所指向的是同一个对象,所以任意一个变量操作了对象的属性,都会影响另一个变量

思考

如何创建一个对象,将目标对象的内容复制过来而不是直接拷贝引用呢?

--- 这里就要介绍我们的深浅拷贝

浅拷贝

介绍

浅拷贝会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝

对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,也就是将该属性值复制一份给新的对象。因为是两份不同的数据,所以对其中一个对象的该成员变量值进行修改,不会影响另一个对象拷贝得到的数据
对于数据类型是引用数据类型的成员变量,比如说成员变量是某个数组、某个类的对象等,那么浅拷贝会进行引用传递,也就是只是将该成员变量的引用值(内存地址)复制一份给新的对象。因为实际上两个对象的该成员变量都指向同一个实例。在这种情况下,在一个对象中修改该成员变量会影响到另一个对象的该成员变量值。

 

浅拷贝的步骤

Object 中已经有 clone() 克隆方法因为是 native 方法,底层已经实现了拷贝对象的逻辑,只要重写即可

注意一定要标注 Cloneable接口,说明这个类对象是可以被拷贝的

具体案例如下:

class Money{
    public double money;

    public Money() {
        this.money = 13.14;
    }

    public Money(double money) {
        this.money = money;
    }

    void MoneyPrint(){
        System.out.println(" Money: "+money);
    }
}


class Student implements Cloneable{
    public String name;
    public int age;
    public Money m = new Money();

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    protected Object clone(){
        try {
            return super.clone();
        }catch (CloneNotSupportedException e){
            throw new AssertionError();
        }
    }

    public void PrintStudent(){
        System.out.print("Student name: " + name+" age: " + age);
        m.MoneyPrint();
    }

}

class Test{
    public static void main(String[] args) {
        Student student = new Student("张三",18);
        student.PrintStudent();
        Student student1 = (Student) student.clone();
        student1.PrintStudent();
        student1.m.money  = 3.14;
        student1.name = "李四";
        student1.age = 20;
        System.out.println("=============");
        student1.PrintStudent();
        student.PrintStudent();
        System.out.println("=============");
        System.out.println(student.m);
        System.out.println(student1.m);
    }
}

这里再提一下写代码过程中的注意事项:
<1> 因为使用的是 Object 中的 clone(),编译器调用的时候会抛出异常,这里一定要写异常捕捉的·代码否则会报错

    @Override
    protected Object clone(){
        try {
            return super.clone();
        }catch (CloneNotSupportedException e){
            throw new AssertionError();
        }
    }

<2> 因为我们调用的是 Object 的克隆,所以类型是 Object,这里就会出现类型不匹配的问题,只需向下转型即可

Student student1 = (Student) student.clone();

输出结果:

Student name: 张三 age: 18 Money: 13.14
Student name: 张三 age: 18 Money: 13.14
=============
Student name: 李四 age: 20 Money: 3.14
Student name: 张三 age: 18 Money: 3.14
=============
com.thz.Money@70177ecd
com.thz.Money@70177ecd

通过以上结果以上结论的正确性:
我们的基础类型是两个不同的对象,而引用类型是共用一个对象(两个Money的地址相同)

 

思考

那么如何让浅拷贝进行的更彻底呢?-- 将共用的引用空间也进行拷贝,让两个对象互不干扰

深拷贝

介绍

深拷贝是一种完全拷贝,无论是基本类型还是引用类型都会完完全全的拷贝一份,在内存中生成一个新的对象。这样拷贝对象和被拷贝对象没有任何关系,互不影响

那么深拷贝该如何实现呢?

-- 我们可以在浅拷贝的基础上,对其内部的引用对象进行拷贝即可

深拷贝与通过重写 clone 方法实现浅拷贝的基本思路一样,只需要为对象图的每一层的每一个对象都实现 Cloneable 接口并重写 clone 方法,最后在最顶层的类的重写的 clone 方法中调用所有的 clone 方法即可实现深拷贝。简单的说就是:每一层的每个对象都进行浅拷贝就是深拷贝

案例如下:

class Money implements Cloneable {
    public double money;

    public Money() {
        this.money = 13.14;
    }

    public Money(double money) {
        this.money = money;
    }

    void MoneyPrint(){
        System.out.println(" Money: "+money);
    }

    @Override
    protected Object clone(){
        try {
            return super.clone();
        }catch (CloneNotSupportedException e){
            throw new AssertionError();
        }
    }
}


class Student implements Cloneable{
    public String name;
    public int age;
    public Money m = new Money();

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    protected Object clone(){
        try {
            Student tmp = (Student)super.clone();
            tmp.m = (Money)m.clone();
            return tmp;
        }catch (CloneNotSupportedException e){
            throw new AssertionError();
        }
    }

    public void PrintStudent(){
        System.out.print("Student name: " + name+" age: " + age);
        m.MoneyPrint();
    }

}

class Test{
    public static void main(String[] args) {
        Student student = new Student("张三",18);
        student.PrintStudent();
        Student student1 = (Student) student.clone();
        student1.PrintStudent();
        student1.m.money  = 3.14;
        student1.name = "李四";
        student1.age = 20;
        System.out.println("=============");
        student1.PrintStudent();
        student.PrintStudent();
        System.out.println("=============");
        System.out.println(student.m);
        System.out.println(student1.m);
    }
}

输出结果

Student name: 张三 age: 18 Money: 13.14
Student name: 张三 age: 18 Money: 13.14
=============
Student name: 李四 age: 20 Money: 3.14
Student name: 张三 age: 18 Money: 13.14
=============
com.thz.Money@70177ecd
com.thz.Money@1e80bfe8

通过以上案例可以知道:
我们在浅拷贝的基础上重写了引用对象 Moeny 的克隆方法,并且在 Student 的拷贝中调用了这个方法。此时两个对象的 Money 的地址就不同了,所以当一个类中所有每个成员都实现克隆,并且在最顶层的 clone
  中调用,那么就是深拷贝 ~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

烟雨长虹,孤鹜齐飞

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值