花了几个小时整理了这篇文,入门浅析深拷贝和浅拷贝,希望此文让你受益!

如果转摘,请注明出处,整理耗时,还忘尊重.


目录

什么是深浅拷贝?

1.浅拷贝

2.深拷贝——Cloneable

3.深拷贝——序列化


什么是深浅拷贝?

我的理解就是:

浅拷贝——拷贝的太浅了,只拷贝对象引用,即对象的地址

深拷贝——拷贝的很深,复制对象的值到新开辟的空间,副本和原来的值没有任何耦合,真正意义的‘拷贝’

直接上代码

代码以 学生背书包 为例, 学生类Student,书包类Bag

1.浅拷贝

public class Bag {

    private String name;
    private String color;

    public String getName() {
        return name;
    }

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

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }
}

使用Clone()方法必须实现 接口 Cloneable , 默认实现的就是浅拷贝(引用拷贝)

public class Student implements Cloneable {

    private String name;
    private int age;
    private Bag bag;

    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 Bag getBag() {
        return bag;
    }

    public void setBag(Bag bag) {
        this.bag = bag;
    }

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

看测试结果,一目了然

 */
public class Test {

    public static void main(String[] args) throws CloneNotSupportedException {
        Bag bag = new Bag();
        bag.setName("耐克1号");
        bag.setColor("红色");

        Student student1 = new Student();
        student1.setName("张小凡");
        student1.setAge(16);
        student1.setBag(bag);

        // Object的clone方法(必须实现Cloneable), 默认是浅拷贝, 注意2点:
        // 1.student2是new地址
        // 2.student2中的非基本数据类型和student1是指向的同一个
        System.out.println("----------------- 浅拷贝测试1 ------------------");
        Student student2 = (Student) student1.clone();
        System.out.println("stu1 == stu2: " + (student1 == student2));                             // false, 不是同一个地址
        System.out.println("stu1.bag == stu2.bag: " + (student1.getBag() == student2.getBag()));   // true 浅拷贝, 拷贝了引用
        System.out.println(JSONObject.toJSONString(student1));
        System.out.println(JSONObject.toJSONString(student2));

        System.out.println("----------------- 浅拷贝测试2 ------------------");
        student1.setName("王五");                                 // 基本数据类型, 不存在引用拷贝
        bag.setName("耐克1号(修补)");                       // student1和student2都输出"耐克1号(修补)", 同一个对象引用
        System.out.println(JSONObject.toJSONString(student1));
        System.out.println(JSONObject.toJSONString(student2));
        System.out.println("bag是否指向同一个:" + (student1.getBag() == student2.getBag())); // true 指向同一个bag

        System.out.println("----------------- 浅拷贝测试3 ------------------");
        Bag bag2 = new Bag();
        bag2.setName("阿迪达斯(新书包)");
        bag2.setColor("蓝色");
        student1.setBag(bag2);      // 当stundet1的bag引用更改了, student1的bag变了, student2还是之前的bag
        System.out.println(JSONObject.toJSONString(student1));
        System.out.println(JSONObject.toJSONString(student2));
        System.out.println("bag 是否指向同一个:" + (student1.getBag() == student2.getBag())); // false student1的bag是新的对象

    }
}

运行如下:

stu1 == stu2: false
stu1.bag == stu2.bag: true
{"age":16,"bag":{"color":"红色","name":"耐克1号"},"name":"张小凡"}
{"age":16,"bag":{"color":"红色","name":"耐克1号"},"name":"张小凡"}
----------------- 浅拷贝测试2 ------------------
{"age":16,"bag":{"color":"红色","name":"耐克1号(修补)"},"name":"王五"}
{"age":16,"bag":{"color":"红色","name":"耐克1号(修补)"},"name":"张小凡"}
bag是否指向同一个:true
----------------- 浅拷贝测试3 ------------------
{"age":16,"bag":{"color":"蓝色","name":"阿迪达斯(新书包)"},"name":"王五"}
{"age":16,"bag":{"color":"红色","name":"耐克1号(修补)"},"name":"张小凡"}
bag 是否指向同一个:false

2.深拷贝——Cloneable

还是上面的Student和Bag类,我们可以看到在浅拷贝Studennt的时候, Bag是非基本数据类,浅拷贝的是这个Bag的引用,指向同一个地址

那么深拷贝的话,同样需要将Bag复制一份到新的地址,实现Cloneable的类本身是进行深拷贝的,默认浅拷贝的方法是拷贝的该对象里面的非基本数据类型,那么如果要将Student进行深拷贝,那么Bag也需要像Student一样实现Cloneable接口,并重写Clone()

修改Bag.java

public class Bag implements Cloneable {

    private String name;
    private String color;
   
   // 省略get/set

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

}

Bag也实现了Cloneable,那么在调用Student.clone()的时候,首先要使用Bag.clone()将bag的对象进行一次拷贝

public class Student implements Cloneable {

    private String name;
    private int age;
    private Bag bag;

    // 省略get/set

    @Override
    public Object clone() throws CloneNotSupportedException {
        Student student = (Student) super.clone();
        // bag 需要实现 Cloneable, [强调]注意: Bag 这里只有基本数据类
        // 如果要实现完全深拷贝, Teacher类中只能含有非基本数据类型, 如果有非基本数据类, 那么在 bag.clone()中必须再做一次类似的深拷贝复制
        student.setBag((Bag) this.bag.clone());
        return student;
    }
}

测试代码:

public class Test {

    public static void main(String[] args) throws CloneNotSupportedException {
        Bag bag = new Bag();
        bag.setName("耐克1号");
        bag.setColor("红色");

        Student student1 = new Student();
        student1.setName("张小凡");
        student1.setAge(16);
        student1.setBag(bag);

        // student2中的非基本数据类型Bag(书包)已经实现了深拷贝
        System.out.println("----------------- 深拷贝测试1 ------------------");
        Student student2 = (Student) student1.clone();
        System.out.println("stu1 == stu2: " + (student1 == student2));                             // false, 不是同一个地址
        System.out.println("stu1.bag == stu2.bag: " + (student1.getBag() == student2.getBag()));   //  false 深拷贝, 不同引用
        System.out.println(JSONObject.toJSONString(student1));
        System.out.println(JSONObject.toJSONString(student2));

        System.out.println("----------------- 深拷贝测试2 ------------------");
        student1.setName("王五");                                 // 基本数据类型, 不存在引用拷贝
        bag.setName("耐克1号(修补)");                            // 深拷贝, student1和student2中的bag没有关系了
        System.out.println(JSONObject.toJSONString(student1));
        System.out.println(JSONObject.toJSONString(student2));
        System.out.println("bag是否指向同一个:" + (student1.getBag() == student2.getBag())); // flase 深拷贝
    }

}

测试结果如下:

----------------- 深拷贝测试1 ------------------
stu1 == stu2: false
stu1.bag == stu2.bag: false
{"age":16,"bag":{"color":"红色","name":"耐克1号"},"name":"张小凡"}
{"age":16,"bag":{"color":"红色","name":"耐克1号"},"name":"张小凡"}
----------------- 深拷贝测试2 ------------------
{"age":16,"bag":{"color":"红色","name":"耐克1号(修补)"},"name":"王五"}
{"age":16,"bag":{"color":"红色","name":"耐克1号"},"name":"张小凡"}
bag是否指向同一个:false

至此深拷贝和浅拷贝基本看完了,但是,我要说的是,如果Bag类型,里面也含有非基本数据类型呢?直接用上面的深拷贝代码,肯定是不行,不信请看代码:

假如学生背书包,书包里面有笔(钢笔、圆珠笔、铅笔...)

public class Pen {
    private String type; // 笔类型
    private String color;

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }
}
public class Bag implements Cloneable {

    private String name;
    private String color;
    private Pen pen;

    public String getName() {
        return name;
    }

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

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public Pen getPen() {
        return pen;
    }

    public void setPen(Pen pen) {
        this.pen = pen;
    }

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

}
public class Student implements Cloneable {

    private String name;
    private int age;
    private Bag bag;

    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 Bag getBag() {
        return bag;
    }

    public void setBag(Bag bag) {
        this.bag = bag;
    }

    @Override
    public Object clone() throws CloneNotSupportedException {
        Student student = (Student) super.clone();
        student.setBag((Bag) this.bag.clone()); // bag 需要实现 Cloneable
        return student;
    }
}

测试代码:

public class Test {

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

        Pen pen = new Pen();
        pen.setType("圆珠笔");
        pen.setColor("黑色");

        Bag bag = new Bag();
        bag.setName("耐克1号");
        bag.setColor("红色");
        bag.setPen(pen);

        Student student1 = new Student();
        student1.setName("张小凡");
        student1.setAge(16);
        student1.setBag(bag);


        // student2中的非基本数据类型Bag(书包)已经实现了深拷贝,但是Bag中的Pen(笔)没有重写Clonable.clone(),无法深拷贝Pen
        System.out.println("----------------- 深拷贝测试1 ------------------");
        Student student2 = (Student) student1.clone();
        System.out.println("stu1 == stu2: " + (student1 == student2));                             // false, 不是同一个地址
        System.out.println("stu1.bag == stu2.bag: " + (student1.getBag() == student2.getBag()));   //  false 深拷贝, 不同引用

        // 特别注意: 下面为true, 虽然是深拷贝, 但深拷贝对象的非基本数据类型仍含有非基本数据类型,导致完全深拷贝失败, 由此我们可以发现:
        // clone() 深拷贝不适合嵌套对象, 特别是嵌套类型很多的情况, 因为对这些类实现深拷贝, 每一个类都需要重写Clonable.clone()方法
        // 因此我们可以选择另外一种方式实现深拷贝————序列化
        System.out.println("注意: tu1.bag.pen == stu2.bag.pen:" + (student1.getBag().getPen() == student2.getBag().getPen()));

        System.out.println(JSONObject.toJSONString(student1));
        System.out.println(JSONObject.toJSONString(student2));

        System.out.println("----------------- 深拷贝测试2 ------------------");
        pen.setType("钢笔");
        pen.setColor("红色");
        System.out.println(JSONObject.toJSONString(student1));
        System.out.println(JSONObject.toJSONString(student2));
        System.out.println("bag是否指向同一个:" + (student1.getBag() == student2.getBag()));


    }

}

测试结果:

----------------- 深拷贝测试1 ------------------
stu1 == stu2: false
stu1.bag == stu2.bag: false
注意: tu1.bag.pen == stu2.bag.pen:true
{"age":16,"bag":{"color":"红色","name":"耐克1号","pen":{"color":"黑色","type":"圆珠笔"}},"name":"张小凡"}
{"age":16,"bag":{"color":"红色","name":"耐克1号","pen":{"color":"黑色","type":"圆珠笔"}},"name":"张小凡"}
----------------- 深拷贝测试2 ------------------
{"age":16,"bag":{"color":"红色","name":"耐克1号","pen":{"color":"红色","type":"钢笔"}},"name":"张小凡"}
{"age":16,"bag":{"color":"红色","name":"耐克1号","pen":{"color":"红色","type":"钢笔"}},"name":"张小凡"}
pen是否指向同一个:true

由此看来,我们直接这么做是无法完成深拷贝的,因为上述代码,并不是完全的深拷贝,问题就出在书包Bag中的成员Pen

  【入门+基础】Java浅拷贝和深拷贝,此文足矣.

既然发现了问题,那么要实现完全的深拷贝就简单了

1.将Pen.java也实现cloneable接口,并且重写clone()

2.在Bag.java中的clone()方法使用pen.clone()深拷贝一份到bag中的pen成员中

问题又来了,如果Pen中又有一个非基本数据类型呢?岂不是又要将Pen中的非基本数据类型也要做同样的操作?

是的,object的clone()方法,在深拷贝的时候,不适用于有对象多层嵌套的情况。

3.深拷贝——序列化

上面已经暴露了简单使用clone()进行深拷贝的弊端,这里介绍另一种深拷贝的方式——序列化深拷贝

所有bean必须实现 Serializable

public class Pen implements Serializable {
    private String type; // 笔类型
    private String color;
    // 省略get/set
}
public class Bag implements Serializable {

    private String name;
    private String color;
    private Pen pen;
    // ...
}

我们可以直接在Student中实现序列化【方法一】

public class Student implements Serializable {

    private String name;
    private int age;
    private Bag bag;

    // ...get/set

    public Object deepClone() throws IOException, ClassNotFoundException {
        // 序列化
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(this);
        oos.close();

        // 反序列化: 分配内存, 写入原始对象, 生成新对象
        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bis);
        Object object = ois.readObject();
        return object;
    }
}

或者,我们直接将深拷贝方法包装成一个工具类【方法二:推荐该方法】:

public class CloneUtils {
    @SuppressWarnings("unchecked")
    public static <T extends Serializable> T deepClone(T obj) {
        T cloneObj = null;
        try {
            //写入字节流
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            ObjectOutputStream obs = new ObjectOutputStream(out);
            obs.writeObject(obj);
            obs.close();

            //分配内存,写入原始对象,生成新对象
            ByteArrayInputStream ios = new ByteArrayInputStream(out.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(ios);
            //返回生成的新对象
            cloneObj = (T) ois.readObject();
            ois.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return cloneObj;
    }

}

测试代码:

public class Test {

    public static void main(String[] args) throws Exception {

        Pen pen = new Pen();
        pen.setType("圆珠笔");
        pen.setColor("黑色");

        Bag bag = new Bag();
        bag.setName("耐克1号");
        bag.setColor("红色");
        bag.setPen(pen);

        Student student1 = new Student();
        student1.setName("张小凡");
        student1.setAge(16);
        student1.setBag(bag);

        // 序列化——深拷贝
        // 相当于重写字节流, 再创建新对象,  跟原对象没有任何引用共享, 无需嵌套重现 Cloneable.clone(), 只需要实现 Serializable (每个子类)
        System.out.println("----------------- 序列化-深拷贝测试1 ------------------");
        // Student student2 = (Student) student1.deepClone(); // 方法一
        Student student2 = CloneUtils.deepClone(student1); // 方法二: 使用工具

        System.out.println("stu1 == stu2: " + (student1 == student2));
        System.out.println("stu1.bag == stu2.bag: " + (student1.getBag() == student2.getBag()));
        System.out.println("stu1.bag.pen == stu2.bag.pen: " + (student1.getBag().getPen() == student2.getBag().getPen()));

        System.out.println(JSONObject.toJSONString(student1));
        System.out.println(JSONObject.toJSONString(student2));

        System.out.println("----------------- 序列化-深拷贝测试2 ------------------");
        pen.setType("钢笔");
        pen.setColor("红色");
        System.out.println(JSONObject.toJSONString(student1));
        System.out.println(JSONObject.toJSONString(student2));

    }

}

测试结果如下:

----------------- 序列化-深拷贝测试1 ------------------
stu1 == stu2: false
stu1.bag == stu2.bag: false
stu1.bag.pen == stu2.bag.pen: false
{"age":16,"bag":{"color":"红色","name":"耐克1号","pen":{"color":"黑色","type":"圆珠笔"}},"name":"张小凡"}
{"age":16,"bag":{"color":"红色","name":"耐克1号","pen":{"color":"黑色","type":"圆珠笔"}},"name":"张小凡"}
----------------- 序列化-深拷贝测试2 ------------------
{"age":16,"bag":{"color":"红色","name":"耐克1号","pen":{"color":"红色","type":"钢笔"}},"name":"张小凡"}
{"age":16,"bag":{"color":"红色","name":"耐克1号","pen":{"color":"黑色","type":"圆珠笔"}},"name":"张小凡"}

总结:

浅拷贝:
	假如 Student 实现了 Cloneable , 重写了 clone()
	那么 Student2 student2=(Stundet) student1.clone(); 就是一个浅拷贝
	浅拷贝特点:
	1.对象本身是新对象
	2.对象里面的基本数据会复制, 基本数据不存在引用
	3.对象里面的非基本数据会进行浅拷贝, 多个对象的某个非基本数据类型的属性, 指向的同一个引用

深拷贝: 
	所有属性都是一份拷贝, 跟原数据不会有任何耦合(不存在引用共享)

序列化深拷贝: 不需要递归让所有对象实现cloneable接口, 方便简洁

如果有问题,还望指出,我会及时更正!

相关文章: