0
点赞
收藏
分享

微信扫一扫

一篇文章带你彻底理解Java中的克隆和拷贝

目录

引子 

克隆方法的第一次尝试

浅拷贝的实现

 深拷贝的实现


新的一天,让我们一起学习,一起冲冲冲😎

引子 

🌰下面由一个栗子引出我们今天的话题

class Student {
    public long id = 1024;
    
    @Override // 重写父类Object的toString方法,以便在主函数里println()可以直接打印出对象的各个属性,原因我们上篇博客已经讲过了
    public String toString() {
        return "Student{" +
                "id=" + id +
                '}';
    }
}
public class test3 {
    public static void main(String[] args) {
        Student student1 = new Student();
        System.out.println(student1); // 因为我们重写了父类Object的toString方法,所以可以直接打印出对象的值来
    }
}

 

😁如图我们自定义了一个学生类并且实例化了一个对象student1,在内存中就是这样:

 

 那么如果我们想在堆内存中对student1对象拷贝一份怎么办呢???

🍑可能有同学会直接写下这样的代码

 

 📝但这只是我们在栈上重新定义了一个引用变量student2,并指向了堆上的student1对象,并没有对我们的student1实现拷贝😂

 

 🏀不信的话,你可以试试,看看当我们改变student2中的id属性时,student1的属性会不会变

class Student {
    public long id = 1024;

    @Override 
    public String toString() {
        return "Student{" +
                "id=" + id +
                '}';
    }
}
public class test3 {
    public static void main(String[] args) {
        Student student1 = new Student();
        Student student2 = student1;

        System.out.print("改变前的:");
        System.out.print(student1); 
        System.out.println(student2);
        
        student2.id = 777;
        System.out.print("改变后的:");
        System.out.print(student1); 
        System.out.println(student2);
    }
}


 

克隆方法的第一次尝试

🍑那么我们该怎样拷贝students1这个对象呢?我们可以运用克隆方法clone()来进行拷贝

 

 别着急😂,我们先看看Cloneable这个接口的源码再说

 

 📝那Clone存在的意义是什么呀?——》他是一个标记接口,说明了该类的对象是可以被克隆的。

 🐟🐟🐟🐟🐟🐟🐟🐟🐟🐟🐟🐟

那好我们就重写父类Object的clone方法,至于怎么重写:在IDEA里就有相应的快捷键来直接生成我们的重写方法——》ctrl+O

 

 

 🌰在内存中就是这样:

😁来看看此时的代码 

 

🍑这时你会发现你的student1可以student1.clone了,那么这就好了吗?

 

 

🍑这时我们再按下回车键,编译器自动就给我们处理了这个异常

 

😁好了,经过一番挫折,我们终于成功的把student1这个对象拷贝了一份。总结一下就是:


浅拷贝的实现

🏀但如果我们在Student类中再定义一个引用类型呢?

 

🏀此时在内存中就变成了这样:

🏀我们可以用代码验证一下:

class Money {
    int money = 17;
 }
class Student extends test3 implements Cloneable{
    public long id = 1024;
    Money m = new Money();
    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                '}';
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
public class test3 {
    public static void main(String[] args) throws CloneNotSupportedException {
        Student student1 = new Student();
        Student student2 = (Student) student1.clone();

        System.out.print("改变前的:");
        System.out.print(student1.m.money + "  ");
        System.out.println(student2.m.money);

        student1.m.money = 98;
        System.out.print("改变后的:");
        System.out.print(student1.m.money + "  ");
        System.out.println(student2.m.money);
    }
}

 

📝这种就称作浅拷贝 ,然后我们就可以引出浅拷贝的概念了:


 

深拷贝的实现

 那怎样把我们的Money类对象也在堆上上拷贝一份呢?

🌰于是代码就变成了这样

class Money implements Cloneable{  // 对Money类实现Cloneable接口
    int money = 17;

     @Override
     protected Object clone() throws CloneNotSupportedException {
         return super.clone(); // 重写clone方法
     }
 }
class Student extends test3 implements Cloneable{
    public long id = 1024;
    Money m = new Money();
    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                '}';
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        // 此时我们在进行 “(Student) student1.clone();” 操作,我们在堆上对student1克隆拷贝出来一个新对象,并让引用变量tmp指向新对象
        // 之前我们是return super.clone(),然后再student1.clone()克隆student1对象,这和直接super.clone是一样的,都是重写了父类Object的clone方法后进行克隆
        Student tmp = (Student) super.clone();

        // 这里的this就是我们要克隆的student1对象,他的一个引用变量m也指向了一个Money类对象,
        // 我们刚才对Money类进行了处理所以可以用this.m.clone对引用变量m所指向的Money类对象进行克隆
        tmp.m = (Money) this.m.clone(); // 把把克隆出来的tmp中的money也给克隆一份,因为tmp.m是Money类型的所以要强制类型转换

        //以前只是把把student1对象的各个值克隆一份,引用变量m所对应的Money类对象并没有克隆,现在是把已经克隆好 Money类对象 的 tmp来返回
        return tmp;
    }
}
public class test3 {
    public static void main(String[] args) throws CloneNotSupportedException { // 处理克隆方法clone的异常
        Student student1 = new Student();
        Student student2 = (Student) student1.clone(); // 此时的student.clone返回Student类对象的引用tmp
        // 这样的话,student2 就指向了原来tmp所指向的对象
        System.out.print("改变前的:");
        System.out.print(student1.m.money + "  ");
        System.out.println(student2.m.money);

        student1.m.money = 98;
        System.out.print("改变后的:");
        System.out.print(student1.m.money + "  ");
        System.out.println(student2.m.money);
    }
}

 

📝在这个代码中有几个关键点需要解释一下:

 

 🌰第一步分析:

 

🌰第二步分析

 

🌰第三步分析

 

🌰第四步分析

上面的拷贝就把引用变量m所指向的Money类的对象也在堆中拷贝了一份,这就是深拷贝,

深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。 

📝总结一下就是

 

🍑好了今天我们就学到这里,铁汁们咱们下篇聊聊Java中Comparable接口和Comparator 接口,看看这两个小家伙是怎么用的,嘻嘻😁

举报

相关推荐

0 条评论