Java

关于Java的浅拷贝和深拷贝

2020-07-23  本文已影响0人  西敏寺钟声

浅拷贝和深拷贝是什么?

浅拷贝和深拷贝都是针对已经存在了的对象的操作,在java中,基本数据类型有八种,和引用数据类型。在程序中,一般用 = 来做赋值的操作,对于基本数据类型,实际上是拷贝它的值,而对于引用数据类型,则是拷贝的它的引用地址,举例如下:

@Test
public void m2() {
    int i = 1;
    int j = i;
    System.out.println(j);

    Student stu1 = new Student("zhangsan", 12);
    System.out.println(stu1);
    Student stu2 = stu1;
    stu2.setAge(20);
    System.out.println(stu1);
    System.out.println(stu2);
}

运行结果:

1
Student{name='zhangsan', age=12, active=null}
Student{name='zhangsan', age=20, active=null}
Student{name='zhangsan', age=20, active=null}

从运行的结果,发现一个问题,int类型的值不用多说,而在引用类型中,明明修改的是stu2的age,为什么stu1的age也发生了变化,这里就涉及到了值传递和引用传递的问题了,ok,后面介绍。

Java中的clone()方法

说到拷贝操作,那java必定提供了API来供我们使用,那就是Object类中的clone()方法了(它是一个native方法),既然是超类中的protected方法,那么子类中就有必要重写一下,所有使用调用clone()来拷贝的时候,其对象必须要实现标识接口Cloneable,否则就会抛出CloneNotSupportedException这个异常。

浅拷贝

首先创建一个Studen类,并实现Cloneable接口,重写clone方法。

public class Student implements Cloneable {

    private String name;

    private int age;

    private Active active;

    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    // 省略get、set方法

}

Active类的实现:

public class Active implements Cloneable {

    private String name;

    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    public Active(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

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

测试用例:用clone方法,复制新new出来的student1,得到student2,再做各种比较。

@Test
public void m1() throws Exception {
    Student student1 = new Student();
    student1.setAge(10);
    student1.setName("zhangsan");
    student1.setActive(new Active("pingpang"));
    // 原始对象的hashcode
    System.out.println(student1.hashCode());
    // 克隆出来的对象
    Student student2 = (Student) student1.clone();
    // 克隆出来的对象的hashcode
    System.out.println(student2.hashCode());
    // 两个对象是否一样
    System.out.println(student1 == student2);
    // 两个对象比较
    System.out.println(student1);
    System.out.println(student2);
    // 两个对象各自的引用比较
    System.out.println(student1.getActive());
    System.out.println(student2.getActive());
}

运行结果:

697960108
943010986
false
Student@299a06ac
Student@383534aa
Active@6bc168e5
Active@6bc168e5

可以得出关于浅拷贝的几点结论:

深拷贝

Student类,在克隆此类对象的时候,将此对象中的引用属性也克隆一份,只有clone方法不同:

public class Student implements Cloneable {

    private String name;

    private int age;

    private Active active;

    public Object clone() throws CloneNotSupportedException {
        Student student = (Student) super.clone();
        student.active = (Active) this.active.clone();
        return student;
    }

    // get、set方法省略

}

Active类,实现Cloneable接口,并重写了clone方法:

public class Active implements Cloneable {

    private String name;

    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    public Active(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

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

测试用例:

@Test
public void m1() throws Exception {
    Student student1 = new Student();
    student1.setAge(10);
    student1.setName("zhangsan");
    student1.setActive(new Active("pingpang"));
    // 原始对象的hashcode
    System.out.println(student1.hashCode());
    // 克隆出来的对象
    Student student2 = (Student) student1.clone();
    // 克隆出来的对象的hashcode
    System.out.println(student2.hashCode());
    // 两个对象是否一样
    System.out.println(student1 == student2);
    // 两个对象比较
    System.out.println(student1);
    System.out.println(student2);
    // 两个对象各自的引用比较
    System.out.println(student1.getActive());
    System.out.println(student2.getActive());
}

测试结果中显示不同student对象中的active对象也不同了:

697960108
943010986
false
com.nmys.story.interview.copy_interview.Student@299a06ac
com.nmys.story.interview.copy_interview.Student@383534aa
com.nmys.story.interview.copy_interview.Active@6bc168e5
com.nmys.story.interview.copy_interview.Active@7b3300e5

可以得出深拷贝的几点结论:

总结

  1. 一定要实现Cloneable接口。
  2. 重写clone()方法,注意:默认是浅拷贝,这里需要将引用类型进行深拷贝处。
  3. 特殊:String类虽然是引用类型,但是是final类,同时也有字符串常量池的存在,不必进行处理。
  4. 深拷贝可以通过上面所说的clone方法实现,还可以通过序列化来实现。
  5. 当时写测试用例的时候,我这里用了lombok的注解@Data,为的就是不用手写get、set方法了,但测试的时候,发现clone出来的对象的hashcode是一样的,原因是lombok在为我们生成get、set的同时,toString、hashCode、equals等方法都替我们重写了,导致clone出来的类虽然 ==false ,但是hashCode却一样,建议还是手动写比较好。
  6. 最后再总结下,假设,A 类中引用了 B 类,B 类中引用了 C 类,对象分别对应 a,b,c,浅拷贝 a 的结果 a1,b,c。深拷贝 a 的结果 a1,b1,c1。
上一篇下一篇

猜你喜欢

热点阅读