Java中的深拷贝和浅拷贝

时间:2021-12-07 22:47:24

Java中的深拷贝和浅拷贝

首先应该清楚Java中的栈内存和堆内存的区别,比如new出来的对象Person p = new Person()其中p并不是真正的对象,p只是代表的引用(地址),真正的对象存在于堆内存中。
一说起拷贝很多人第一反应该是赋值吧,的确复制也是一种拷贝方式,比如基本数据类型的变量int a = b;这个就达到了在栈内存中拷贝的效果。但是不要忘了堆内存,正如前面所说的情况:p在栈内存中,而p引用的对象实体在堆内存中,如果还是直接以Person p1 = p这样的形式拷贝的话可能就要出问题啦!通过p1访问的Person成员变量,如果在此之前p已经修改过p1需要访问的变量,那么p1访问的就是p修改过的成员变量,也就是说p与p1共享一个对象,并没有达到拷贝的效果。

验证程序如下:


public class Temp {
    public static void main(String[] args) {
        Person p = new Person("张三");
        Person p1 = p;
        System.out.println(p1.getName());
        p.setName("李四");
        System.out.println(p1.getName());
    }
}

class Person {
    private String name;

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

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

    public String getName() {
        return this.name;
    }
}

运行结果:
张三
李四

Java的Object类中提供了clone()方法,此方法就是用来拷贝操作的,但它是一种浅拷贝,如果要达到深拷贝效果的话,还得添加一些后续操作。下面我们来详细讨论一下。
深拷贝与浅拷贝的区别是:深拷贝需要在内存中再开辟一部分空间来存放对象,与之前的对象完全独立,没有任何共享的区域。

代码示例:

public class Test {
    public static void main(String[] args) throws CloneNotSupportedException {
        Person p1 = new Person(20, "脚");
        Person p2 = (Person) p1.clone();
        System.out.println(p1);
        System.out.println(p2);
    }
}

class Person implements Cloneable {
    private int age;
    private WalkWay way ;

    public Person(int age, String way) {
        this.age = age;
        this.way = new WalkWay(way);
    }

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

    public int getAge() {
        return this.age;
    }

    public WalkWay getWalkWay() {
        return this.way;
    }

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

class WalkWay {
    private String way;

    public WalkWay(String way) {
        this.way = way;
    }

    public void setWay(String way) {
        this.way = way;
    }

    public String getWay() {
        return this.way;
    }
}

运行结果:
Person@2a139a55
Person@15db9742

由以上代码的运行结果可以看出p1与p2所引用的对象在堆内存中处于不同的两块区域中,但是它们真的完全独立吗? 下面我们来验证一下,将上面给程序main方法稍作修改,代码如下:

public class Test {
    public static void main(String[] args) throws CloneNotSupportedException {
        Person p1 = new Person(20, "脚");
        Person p2 = (Person) p1.clone();
        System.out.println(p1.getWalkWay() == p2.getWalkWay());
        p1.getWalkWay().setWay("手");
        p1.setAge(21);
        System.out.println("p1 " + p1.getWalkWay().getWay() + " " + p1.getAge());
        System.out.println("p2 " + p2.getWalkWay().getWay() + " " + p2.getAge());
    }
}

运行结果:
true
p1 手 21
p2 手 20

很明显p1和p2并没有实现完全独立,而且我们发现其中基本数据类型是完全独立的,p1与p2之间没有影响;问题就出在引用类型上,p1将自己的way修改为”手”,p2的way也跟着变了,说明它们引用的是同一个对象。
因此我们可以判断出Object类中的clone()方法本身实现的是浅拷贝,那如何进行深拷贝呢?先看下面的代码示例:

public class Test {
    public static void main(String[] args) throws CloneNotSupportedException {
        Person p1 = new Person(20, "脚");
        Person p2 = (Person) p1.clone();
        System.out.println(p1.getWalkWay() == p2.getWalkWay());
        p1.getWalkWay().setWay("手");
        p1.setAge(21);
        System.out.println("p1 " + p1.getWalkWay().getWay() + " " + p1.getAge());
        System.out.println("p2 " + p2.getWalkWay().getWay() + " " + p2.getAge());
    }
}

class Person implements Cloneable {
    private int age;
    private WalkWay way ;

    public Person(int age, String way) {
        this.age = age;
        this.way = new WalkWay(way);
    }

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

    public int getAge() {
        return this.age;
    }

    public WalkWay getWalkWay() {
        return this.way;
    }

    protected Object clone() throws CloneNotSupportedException {
        Person p = (Person) super.clone();
        p.way = (WalkWay) p.way.clone();
        return p;
    }
}

class WalkWay implements Cloneable {
    private String way;

    public WalkWay(String way) {
        this.way = way;
    }

    public void setWay(String way) {
        this.way = way;
    }

    public String getWay() {
        return this.way;
    }

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

运行结果:
false
p1 手 21
p2 脚 20

如果要clone的对象中含有引用类型的变量,那么既要对要克隆对象的类实现Cloneable接口,还要对该类中的引用类型的类实现Cloneable接口,正如上面的示例,对Person和WalkWay都要implements Cloneable,并且都要重写clone()方法,对于WalkWay而言只需要调用父类的clone()方法即super.clone(),然后返回就可以了。
可能有人会说,String也是引用类型啊,为什么不对它实现 Cloneable接口呢?String类是final修饰的,不能被继承,其实我们可以用其他方式来对String进行深拷贝。

修改如下:

public class Test {
    public static void main(String[] args) throws CloneNotSupportedException {
        Person p1 = new Person(20, "脚");
        Person p2 = (Person) p1.clone();
        System.out.println(p1.getWalkWay().getWay() == p2.getWalkWay().getWay());
    }
}

class Person implements Cloneable {
    private int age;
    private WalkWay way ;

    public Person(int age, String way) {
        this.age = age;
        this.way = new WalkWay(way);
    }

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

    public int getAge() {
        return this.age;
    }

    public WalkWay getWalkWay() {
        return this.way;
    }

    protected Object clone() throws CloneNotSupportedException {
        Person p = (Person) super.clone();
        p.way = (WalkWay) p.way.clone();
        return p;
    }
}

class WalkWay implements Cloneable {
    private String way;

    public WalkWay(String way) {
        this.way = way;
    }

    public void setWay(String way) {
        this.way = way;
    }

    public String getWay() {
        return this.way;
    }

    protected Object clone() throws CloneNotSupportedException {
        WalkWay walkWay = (WalkWay) super.clone();
        walkWay.way = new String(way);
        return walkWay; 
    }
}

还有一种方式:

public class Test {
    public static void main(String[] args) throws CloneNotSupportedException {
        Person p1 = new Person(20, "脚");
        Person p2 = (Person) p1.clone();
        System.out.println(p1.getWalkWay().getWay() == p2.getWalkWay().getWay());
    }
}

class Person implements Cloneable {
    private int age;
    private WalkWay way ;

    public Person(int age, String way) {
        this.age = age;
        this.way = new WalkWay(way);
    }

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

    public int getAge() {
        return this.age;
    }

    public WalkWay getWalkWay() {
        return this.way;
    }

    protected Object clone() throws CloneNotSupportedException {
        Person p = (Person) super.clone();
        p.way = (WalkWay) p.way.clone();
        return p;
    }
}

class WalkWay implements Cloneable {
    private String way;

    public WalkWay(String way) {
        this.way = new String(way);
    }

    public void setWay(String way) {
        this.way = new String(way);
    }

    public String getWay() {
        return this.way;
    }

    protected Object clone() throws CloneNotSupportedException {
        WalkWay walkWay = (WalkWay) super.clone();
// walkWay.way = new String(way);
        return walkWay; 
    }
}

这两种方式可以说是利用String类型数据在内存中的存储原理来实现的。