JAVA深拷贝和浅拷贝

简介: JAVA深拷贝和浅拷贝

我们知道,一个实体类通过实现Cloneable接口可获得对象拷贝的能力。而拷贝又分为深拷贝和浅拷贝。很幸运,通过本文你可以了解到它们的区别。

首先,我们先来看这样一段代码:

@Data
public class Person implements Cloneable {
 
    private String name;
 
    private Person father;
 
    public Person(String name) {
        this.name = name;
    }
 
    public Person(String name, Person person) {
        this.name = name;
        this.father = person;
    }
 
    @Override
    public Person clone() {
        Person p = null;
        try {
            p = (Person) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return p;
    }
 
    public static void main(String[] args) {
        Person father = new Person("父亲");
        Person son = new Person("大儿子", father);
        Person person = (Person) son.clone();
        person.setName("小儿子");
 
        System.out.println("大儿子姓名:" + son.getName());
        System.out.println("小儿子姓名:" + person.getName());
        
    }
}

结果:

大儿子姓名:大儿子
小儿子姓名:小儿子
Process finished with exit code 0

突然有一天,他爹想让小儿子认个干爹,于是:

@Data
public class Person implements Cloneable {
 
    private String name;
 
    private Person father;
 
    public Person(String name) {
        this.name = name;
    }
 
    public Person(String name, Person person) {
        this.name = name;
        this.father = person;
    }
 
    @Override
    public Person clone() {
        Person p = null;
        try {
            p = (Person) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return p;
    }
 
    public static void main(String[] args) {
        Person father = new Person("父亲");
        Person son = new Person("大儿子", father);
        Person person = (Person) son.clone();
        person.setName("小儿子");
 
        System.out.println("大儿子姓名:" + son.getName());
        System.out.println("小儿子姓名:" + person.getName());
 
        person.getFather().setName("干爹");
        System.out.println("大儿子父亲姓名:" + son.getFather().getName());
        System.out.println("小儿子姓名:" + person.getFather().getName());
    }
}

结果:

大儿子姓名:大儿子
小儿子姓名:小儿子
大儿子父亲姓名:干爹
小儿子姓名:干爹
 
Process finished with exit code 0

What? 他爹:我儿子呢?? 我俩儿都认干爹了??

什么原因呢?其实我们可以看到,此处的小儿子是通过大儿子拷贝过来的。我们知道所有类都继承自Object,Object提供了一个对象拷贝的默认方法,即上面代码中的son.clone方法,但是该方法是有缺陷的,它提供的是一种浅拷贝方式,也就是说它并不会把对象的所有属性全部拷贝一份,而是有选择性的拷贝,它的拷贝规则如下:

(1)基本类型如果变量是基本类型,则拷贝其值,比如int、float等。

(2)对象如果变量是一个实例对象,则拷贝地址引用,也就是说此时新拷贝出的对象与原有对象共享该实例变量,不受访问权限的限制。这在Java中是很疯狂的,因为它突破了访问权限的定义:一个private修饰的变量,竟然可以被两个不同的实例对象访问,这让Java的访问权限体系情何以堪!

(3)String字符串这个比较特殊,拷贝的也是一个地址,是个引用,但是在修改时,它会从字符串池(String Pool)中重新生成新的字符串,原有的字符串对象保持不变,在此处我们可以认为String是一个基本类型。

明白了这三个规则,上面的例子就很清晰了,小儿子对象是通过拷贝大儿子产生的,其父亲都是同一个人,也就是同一个对象,大儿子修改了父亲名称,小儿子也就跟着修改了—于是,父亲的两个儿子都没了!其实要更正也很简单,clone方法的代码如下:

    @Override
    public Person clone() {
        Person p = null;
        try {
            p = (Person) super.clone();
            p.setFather(new Person(p.getFather().getName()));
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
 
        return p;
    }

然后再运行,小儿子的父亲就不会是“干爹”了。如此就实现了对象的深拷贝(Deep Clone),保证拷贝出来的对象自成一体,不受“母体”的影响,和new生成的对象没有任何区别。

大儿子姓名:大儿子
小儿子姓名:小儿子
大儿子父亲姓名:父亲
小儿子姓名:干爹
 
Process finished with exit code 0
目录
相关文章
|
6月前
|
Java
Java的浅拷贝与深拷贝
Java的浅拷贝与深拷贝
53 0
|
6月前
|
Java Apache
Java中的深拷贝与浅拷贝
Java中的深拷贝与浅拷贝
45 0
|
5月前
|
Java
“深入探讨Java中的对象拷贝:浅拷贝与深拷贝的差异与应用“
“深入探讨Java中的对象拷贝:浅拷贝与深拷贝的差异与应用“
|
5月前
|
存储 Dubbo Java
一篇文章讲明白Java的深拷贝和浅拷贝
一篇文章讲明白Java的深拷贝和浅拷贝
51 0
|
6月前
|
Java
【JAVA面试题】什么是深拷贝?什么是浅拷贝?
【JAVA面试题】什么是深拷贝?什么是浅拷贝?
|
Java
Java深拷贝和浅拷贝Map对象
Java深拷贝和浅拷贝Map对象
196 0
|
Java
【java面试题】- java深拷贝和浅拷贝区别?什么是引用拷贝?
java深拷贝和浅拷贝区别?什么是引用拷贝?
87 0
Java 最常见的面试题:深拷贝和浅拷贝区别是什么?
Java 最常见的面试题:深拷贝和浅拷贝区别是什么?
|
存储 Java
java深入理解浅拷贝和深拷贝
java深入理解浅拷贝和深拷贝