java中clone方法的理解(深拷贝、浅拷贝)

x33g5p2x  于2022-05-05 转载在 Java  
字(5.2k)|赞(0)|评价(0)|浏览(392)

前言:

java中的clone一直是一个老生常谈的问题,另外关于克隆网上也有很多的写过这方面的问题。
我在这里记录一下我遇到的问题和使用clone的方法。

知识点一:什么是浅拷贝?

我们这里说的浅拷贝是指我们拷贝出来的对象内部的引用类型变量和原来对象内部引用类型变量是同一引用(指向同一对象)。
	但是我们拷贝出来的对象和新对象不是同一对象。

	简单来说,新(拷贝产生)、旧(元对象)对象不同,但是内部如果有引用类型的变量,新、旧对象引用的都是同一引用。

知识点二:什么是深拷贝?

深拷贝:全部拷贝原对象的内容,包括内存的引用类型也进行拷贝

知识点三、java拷贝(clone)的前提:

1.首先我们需要知道Object类中一个clone()的方法,并且是protected关键字修饰的本地方法(使用native关键字修饰),我们完成克隆需要重写该方法。
注意:按照惯例重写的时候一个要将protected修饰符修改为public,这是JDK所推荐的做法,但是我测试了一下,
复写的时候不修改为public也是能够完成拷贝的。但是还是推荐写成public。

2.我们重写的clone方法一个要实现Cloneable接口。虽然这个接口并没有什么方法,但是必须实现该标志接口。
如果不实现将会在运行期间抛出:CloneNotSupportedException异常

3.Object中本地clone()方法,默认是浅拷贝

知识点四:浅拷贝案例:

拷贝类:
package cn.cupcat.java8;

public class Person implements Cloneable{
    private String name;
    private int age;
    private int[] ints;

    public int[] getInts() {
        return ints;
    }

    public Person(String name, int age, int[] ints) {
        this.name = name;
        this.age = age;
        this.ints = ints;
    }

    public void setInts(int[] ints) {
        this.ints = ints;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

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

    /**
     *  默认实现
     * */
    @Override
    public Object clone() throws CloneNotSupportedException {
        return  super.clone();
    }
}
测试类:
package cn.cupcat.java8;

import org.junit.Test;

public class CloneTest  {

    @Test
    public void test() throws CloneNotSupportedException {
        int[] ints = {1,2,3};
        String name = "zhangxiangyang";
        int age = 23;
        Person person = new Person("zhangxiangyang",age,ints);
        System.out.print("一:克隆前:  age = "+ age + "... name = "+ name + " 数组:");
        for (int i : ints){
            System.out.print(i + " ");
        }
        System.out.println();
        
        //拷贝
        Person clonePerson = (Person) person.clone();

        int clonePersonAge = clonePerson.getAge();
        String clonePersonName = clonePerson.getName();
        int[] ints1 = clonePerson.getInts();

        System.out.print("二:克隆后: age = "+ clonePersonAge + "... name = "+ clonePersonName + " 数组: ");
        for (int i : ints1){
            System.out.print(i + " ");
        }
        System.out.println();
        //修改:
        ints1[0] = 50;
        //修饰
        clonePerson.setName("666666666");
        
        age = person.getAge();
        name = person.getName();
        System.out.println();
        System.out.print("三:修改后原对象: age = "+ age + "... name = "+ name + "数组 ");
        for (int i : ints){
            System.out.print(i + " ");
        }
        System.out.println();
        System.out.println("四:person == clonePerson ? "+ (person == clonePerson ));
    }
}

结果为:

一:克隆前:  age = 23... name = zhangxiangyang 数组:1 2 3 
二:克隆后: age = 23... name = zhangxiangyang 数组: 1 2 3 

三:修改后原对象: age = 23... name = zhangxiangyang数组 50 2 3 
四:person == clonePerson ? false
总结:
1.通过四输出  person == clonePerson ? false 可以看出,克隆以后的对象已经和以前不是同一对象了。因为其引用是不同的。
2.通过分析一、二可以看出我们的拷贝成功执行了,并且拷贝的数据也都正确
3.通过分析三,我们修改了拷贝后对象的name、ints
		ints1[0] = 50;
        //修饰
        clonePerson.setName("666666666");
  发现原对象中的ints也跟着修改了,因此可以证明拷贝后的对象和原对象的ints数组指向了同一引用。
  而我们发现同时也修改了拷贝对象name属性,为什么那么原对象中的name属性没有发生改变呢?
  而且String类型也是引用类型呀? 别急,下面我会画图示意。
4. 通过以上总结,我们得出结论:clone()方法的默认实现是浅拷贝
下面通过画图示意:

拷贝成功后:

修改拷贝对象过后:

ps:如果看不清楚图片,可以在标签窗口打开

知识点五:深拷贝案例:

该类只和浅拷贝在clone方法的实现上有区别,其他地方相同:

package cn.cupcat.java8;

public class Person implements Cloneable{
    private String name;
    private int age;
    private int[] ints;

    public int[] getInts() {
        return ints;
    }

    public Person(String name, int age, int[] ints) {
        this.name = name;
        this.age = age;
        this.ints = ints;
    }

    public void setInts(int[] ints) {
        this.ints = ints;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

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

    /**
     *  深拷贝
     * */
    @Override
    public Object clone() throws CloneNotSupportedException {
        Person person = new Person(name,age);
        int[] ints = new int[this.ints.length];
        System.arraycopy(this.ints,0,ints,0,ints.length);
        person.setInts(ints);

        return  person;
    }
}

该测试类和前拷贝测试类相同:

package cn.cupcat.java8;

import org.junit.Test;

public class CloneTest  {

    @Test
    public void test() throws CloneNotSupportedException {
        int[] ints = {1,2,3};
        String name = "zhangxiangyang";
        int age = 23;
        Person person = new Person("zhangxiangyang",age,ints);
        System.out.print("克隆前:  age = "+ age + "... name = "+ name + " 数组:");
        for (int i : ints){
            System.out.print(i + " ");
        }

        System.out.println();

        //拷贝
        Person clonePerson = (Person) person.clone();

        int clonePersonAge = clonePerson.getAge();
        String clonePersonName = clonePerson.getName();
        int[] ints1 = clonePerson.getInts();

        System.out.print("克隆后: age = "+ clonePersonAge + "... name = "+ clonePersonName + " 数组: ");
        for (int i : ints1){
            System.out.print(i + " ");
        }
        System.out.println();
        //修改:
        ints1[0] = 50;
        //修饰可控后的对象
        clonePerson.setName("666666666");
        
        age = person.getAge();
        name = person.getName();
        System.out.println();
        System.out.print("修改后原对象: age = "+ age + "... name = "+ name + "数组 ");
        for (int i : ints){
            System.out.print(i + " ");
        }
        System.out.println();
        System.out.println("person == clonePerson ? "+ (person == clonePerson ));
    }
}
结果为:
一:克隆前:  age = 23... name = zhangxiangyang 数组:1 2 3 
二:克隆后: age = 23... name = zhangxiangyang 数组: 1 2 3 

三:修改后原对象: age = 23... name = zhangxiangyang数组 1 2 3 
四:person == clonePerson ? false
总结:
1.通过四可以看出完成了拷贝,并且克隆对象和原对象不是同一对象(没有同一引用)
2.通过一、二可以看出完成了拷贝并且数据正确
3.通过三,我们修改克隆以后的对象,打印原对象发现没有影响到原对象的数据,也就是说完成的深拷贝
  下面使用图解说明一下
画图说明:

拷贝完成后:

修改拷贝后对象后:

知识点六:总结

拷贝也算是我们经常使用的一个方法,但是如果是不明白其中原理的程序员可能还是会入坑的。下面总结几条使用建议:
 1.一定要实现Cloneable接口
 2.复写clone()方法,注意:默认是浅拷贝,这里需要将引用类型进行深拷贝处理
 3.特殊:String类虽然是引用类型,但是是final类,同时也有字符串常量池的存在,不必进行处理

创作打卡挑战赛

赢取流量/现金/CSDN周边激励大奖

相关文章