JavaSE语法(12)——详细解读java中的 Clonable 接口和深拷贝

1.Clonable 接口

  java中提供了Clonable接口,这个单词翻译过来就是“可克隆的”的意思,显然这是用来帮助“类”进行拷贝的。我们先来看看源码:

image.png

  我去,怎么一个方法都没有?这左看右看就只是一个空接口,这有什么用呢?按照常规思路的话这里面是不是应该有一个clone()方法等着我们去重写,然后直接类.clone()就完事了呀(回想我们拷贝数组的时候使用的是数组.clone())。

  确实是类.clone()这样用,但是没这么简单,这种接口在java中叫“空接口”或者“标记接口”,是用来表示一个类可以被克隆,即这个类有克隆这种功能。

  那么clone()这个方法在哪呢?这个方法在Object中:

image.png

  这个native是什么意思?只要是被native修饰的方法,它的底层是用C/C++语言实现的,我们这里看不到里面的源码。我们只要调用这个方法它会自己帮我们实现克隆。

  那么Object中的clone()Clonable接口有什么关系呢?既然clone()Object中,我们为什么不直接重写呢,这与Clonable接口就是八竿子打不着呀?我们先试一试直接重写不用Clonable接口的情况:

  现在有一个“学生类”,我们重写了Object中的clone(),注意这里的返回值是Object。我们不需要自己重新写一个逻辑,直接return super.clone();就行了,因为在Object中已经帮我们实现了。(ps:throws .... 这叫抛异常,不熟悉没关系,直接跟着写不影响。)

class Student{

    public String name;
    public int age;

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

    //重写Object中的toString(),方便打印。
    public String toString() {
        return "Student: " +
                "name=" + name  +
                ", age=" + age ;
    }

    //重写Object中的clone()方法。
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}


public class Main {
    public static void main(String[] args) throws CloneNotSupportedException {

        Student student1 = new Student("张三",20);
        Student s = (Student)student1.clone();

        System.out.println(student1);
        System.out.println(s);
    }
}
复制代码

  (ps:可能有人会问:Student不是继承了Object了吗,clone() 不就继承到Student里了?在Student中不是就可以不用重写clone()了吗?,直接在mainstudent1.clone()就行了呀。这其实是不行的,因为源码中clone()是被protected修饰的,不重写是访问不到的。如果实在想不清楚可以看看这篇文章->Java protected 关键字详解 | 菜鸟教程 (runoob.com)

  上面代码表面上看是没有什么问题的,我们开始运行:

image.png

  发现抛了一个异常CloneNotSupportedException,大概意思为“不支持克隆异常”,这其实就是该类不支持克隆,或者可以理解为这个类没有克隆这种功能。如果想要实现这个功能,就必须得实现Clonable接口,你可以认为Clonable就是起到一个标记的作用。

  我们修改后的代码:

class Student implements Cloneable {

    public String name;
    public int age;

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


    //重写Object中的toString(),方便打印。
    public String toString() {
        return "Student: " +
                "name=" + name  +
                ", age=" + age ;
    }

    //重写Object中的clone()方法。
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

public class Main {
    public static void main(String[] args) throws CloneNotSupportedException {

        Student student1 = new Student("张三",20);
        Student s = (Student)student1.clone();

        System.out.println(student1);
        System.out.println(s);
    }
}
复制代码

结果:

image.png

  可以看到已经成功了。总结:对类克隆(拷贝)的步骤有,第一实现Cloneable接口;第二重写Object中的clone()方法;第三是要throw异常。


2.深拷贝

  在了解 深拷贝 之前,我们要先了解一下什么是 浅拷贝。我在上面例子的基础上增加一个新的类Money,并在Student类中new一个Money

public class Money {
    public double m = 99.99;
}

class Student implements Cloneable {

    public String name;
    
    public int age;

    //Money实例
    public Money money = new Money();

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


    //重写Object中的toString(),方便打印。
    public String toString() {
        return "Student: " +
                "name=" + name  +
                ", age=" + age ;
    }

    //重写Object中的clone()方法。
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
复制代码

  下面是main方法:

public class Main {
    public static void main(String[] args) throws CloneNotSupportedException {

        Student student1 = new Student("张三",20);
        Student s = (Student) student1.clone();

        //拷贝之后,更改m的值。
        student1.money.m = 30.5;
        //student1中m的值
        System.out.println(student1.money.m);
        //s中m的值
        System.out.println(s.money.m);
    }
}
复制代码

结果:

image.png

  这两个值是相等的,为什么?是因为student1s中的money成员同一个引用! 这就导致修改student1中的money后,s中的money的值也会跟着发生变化。拷贝的过程如下:

拷贝前:

深拷贝-第 2 页.drawio.png

拷贝后:

深拷贝-第 1 页.drawio.png

  这里只是把money的地址拷贝过去了,像上面图片这样的拷贝就是浅拷贝。


  我们想要达到效果是在拷贝的时候让money重新引用一个新的实例:

深拷贝-第 3 页.drawio.png

  如何实现呢?细心想一下,我实现深拷贝的关键 是克隆student1中的money,然后让s中的money引用它。说明money也要实现Cloneable接口并重写Object中的clone()方法,下面给出最终的代码(修改最多的是Student中的clone()方法):

class Money implements Cloneable{
    public double m = 100.00;

    //重写Object中的clone()方法。
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

class Student implements Cloneable {

    public String name;
    public int age;

    //Money实例
    public Money money = new Money();



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


    //重写Object中的toString(),方便打印。
    public String toString() {
        return "Student: " +
                "name=" + name  +
                ", age=" + age ;
    }

    //重写Object中的clone()方法。
    protected Object clone() throws CloneNotSupportedException {

        Student s = (Student) super.clone();
        //在当前例子来看:克隆`student1`中的`money`,然后让`s`中的`money`引用它。
        s.money = (Money) this.money.clone();
        return s;
    }
}
复制代码

  clone()方法的返回值可以是Student类型,只要程序能通过随你怎么写都行。

  Main还是原来的Main

public class Main {
    public static void main(String[] args) throws CloneNotSupportedException {

        Student student1 = new Student("张三",20);
        Student s = (Student) student1.clone();

        //拷贝之后
        student1.money.m = 30.5;
        //student1中m的值
        System.out.println(student1.money.m);
        //s中m的值
        System.out.println(s.money.m);
    }
}
复制代码

结果:

image.png

  成功!

猜你喜欢

转载自juejin.im/post/7190386993212162107