浅拷贝——拷贝的太“浅”了,只拷贝对象引用,即对象的地址
    深拷贝——拷贝的很“深”,复制对象的值到新开辟的空间,副本和原来的值没有任何耦合,真正意义的‘拷贝’

    基本数据类型:Java中8种基本数据类型short, int, long, float, double, char, byte, boolean

    特殊类型:String (这个类型比较重要)
    String 存在于堆内存、常量池;这种比较特殊, 本身没有实现 Cloneable, 传递是引用地址;

    由本身的final性, 每次赋值都是一个新的引用地址,原对象的引用和副本的引用互不影响。
    因此String就和基本数据类型一样,表现出了”深拷贝”特性.

    复合数据类型:不严格的说法就是指非基本类型的复合对象;当然排除JDK自带一些类型,例如Bigdecimal等等;

    01
    浅拷贝

    有Bag实体类,如下

    1. @Data
    2. public class Bag {
    3. private String name;
    4. private String color;
    5. }

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

    1. @Data
    2. public class Student implements Cloneable {
    3. private String name;
    4. private int age;
    5. private Bag bag;
    6. @Override
    7. public Object clone() throws CloneNotSupportedException {
    8. return super.clone();
    9. }
    10. }

    浅拷贝测试结果,一目了然

    @Slf4j
    public class Test {
    
        /**
         * 8种基本类型和String
         * Java中8种基本类型都在栈内存,clone都是深拷贝,不存在浅拷贝(引用拷贝)
         * 第1类:整型--》byte,short,int,long
         * 第2类:浮点--》float,double
         * 第3类:逻辑--》boolean
         * 第4类:字符--》char
         *
         * String 存在于堆内存、常量池;这种比较特殊, 本身没有实现 Cloneable, 传递是引用地址;
         * 由本身的final性, 每次赋值都是一个新的引用地址,原对象的引用和副本的引用互不影响。
         * 因此String就和基本数据类型一样,表现出了"深拷贝"特性.
         */
        public static void main(String[] args) throws CloneNotSupportedException {
    
            // Bag 未实现 Cloneable
            Bag bag = new Bag();
            bag.setName("耐克1号");
            bag.setColor("红色");
    
            // student 实现了 Cloneable
            Student student1 = new Student();
            String name = "张小凡";
            student1.setName(name);
            student1.setAge(18);
            student1.setBag(bag);
    
            // Object的clone方法(必须实现 Cloneable), 默认是浅拷贝(引用拷贝), 注意2点:
            // 1.student2是new地址
            // 2.student2中的复合类型(Bag)和student1中的(Bag)是同一个对象(引用拷贝)
            log.info("----------------- 浅拷贝测试1 ------------------");
            Student student2 = (Student) student1.clone();
            // false, 不是同一个地址
            log.info("student1 == student2: " + (student1 == student2));
            // true 浅拷贝, 引用拷贝
            log.info("student1.bag == student1.bag: " + (student1.getBag() == student2.getBag()));
            log.info(JSONObject.toJSONString(student1));
            log.info(JSONObject.toJSONString(student2));
    
            log.info("----------------- 浅拷贝测试2 ------------------");
            // String类型, 这里并不会改变student1和student2中的的name;
            // String 存在于堆内存、常量池;这种比较特殊, 本身没有实现 Cloneable, 传递是引用地址;
            // 由本身的final性, 每次赋值都是一个新的引用地址,原对象的引用和副本的引用互不影响。
            // 因此String就和基本数据类型一样,表现出了"深拷贝"特性.
            name = "张大凡";
            // student1和student2中的bag.name同时修改, 因为是浅拷贝是同一个引用地址
            bag.setName("耐克1号(修补)");
            log.info(JSONObject.toJSONString(student1));
            log.info(JSONObject.toJSONString(student2));
            // true 指向同一个bag地址
            log.info("bag引用地址是否相同:" + (student1.getBag() == student2.getBag()));
    log.info("----------------- 浅拷贝测试3 ------------------");
            Bag bag2 = new Bag();
            bag2.setName("阿迪达斯(新书包)");
            bag2.setColor("蓝色");
            // 修改stundet1的bag引用不影响student2的bag
            student1.setBag(bag2);
            log.info(JSONObject.toJSONString(student1));
            log.info(JSONObject.toJSONString(student2));
            // false student1的bag是新的对象
            log.info("bag 是否指向同一个:" + (student1.getBag() == student2.getBag()));
        }
    }
    

    运行结果如下:

    student1 == student2: false
    student1.bag == student1.bag: true
    {"age":18,"bag":{"color":"红色","name":"耐克1号"},"name":"张小凡"}
    {"age":18,"bag":{"color":"红色","name":"耐克1号"},"name":"张小凡"}
    ----------------- 浅拷贝测试2 ------------------
    {"age":18,"bag":{"color":"红色","name":"耐克1号(修补)"},"name":"张小凡"}
    {"age":18,"bag":{"color":"红色","name":"耐克1号(修补)"},"name":"张小凡"}
    bag引用地址是否相同:true
    ----------------- 浅拷贝测试3 ------------------
    {"age":18,"bag":{"color":"蓝色","name":"阿迪达斯(新书包)"},"name":"张小凡"}
    {"age":18,"bag":{"color":"红色","name":"耐克1号(修补)"},"name":"张小凡"}
    bag 是否指向同一个:false
    

    02
    深拷贝(Cloneable)

    还是上面的Student和Bag类,我们可以看到在浅拷贝Student的时候, Bag是复杂数据类型时,浅拷贝的是这个Bag的引用,指向同一个地址
    那么深拷贝的话,同样需要将Bag复制一份到新的地址,实现Cloneable的类本身是进行深拷贝的,默认浅拷贝的方法是拷贝的该对象里面的复杂数据类型,那么如果要将Student进行深拷贝,那么Bag也需要像Student一样实现Cloneable接口,并重写Clone()
    修改Bag.java

    @Data
    public class Bag implements Cloneable {
    
        private String name;
        private String color;
    
        @Override
        protected Object clone() throws CloneNotSupportedException {
            return super.clone();
        }
    }
    

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

    @Data
    public class Student implements Cloneable {
    
        private String name;
        private int age;
        private Bag bag;
    
        @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;
        }
    }
    

    测试代码:

    @Slf4j
    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(书包)已经实现了深拷贝
            log.info("----------------- 深拷贝测试1 ------------------");
            Student student2 = (Student) student1.clone();
            // false, 不是同一个地址
            log.info("student1 == student2: " + (student1 == student2));
            // false 深拷贝, 不同引用
            log.info("student1.bag == student2.bag: " + (student1.getBag() == student2.getBag()));
            log.info(JSONObject.toJSONString(student1));
            log.info(JSONObject.toJSONString(student2));
    
            log.info("----------------- 深拷贝测试2 ------------------");
            // String类型, 每次赋值都是一个新对象, 表现的就是深拷贝, 新旧对象互不影响
            student1.setName("王五");
            // 深拷贝, student1和student2中的bag是两个不同的引用, 相互独立
            bag.setName("耐克1号(修补)");
            log.info(JSONObject.toJSONString(student1));
            log.info(JSONObject.toJSONString(student2));
            // flase 深拷贝
            log.info("bag是否指向同一个:" + (student1.getBag() == student2.getBag()));
        }
    }
    

    测试结果:

    ----------------- 深拷贝测试1 ------------------
    student1 == student2: false
    student1.bag == student2.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类型,里面也含有复杂数据类型呢?直接用上面的深拷贝代码,肯定是不行,不信请看代码:

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

    @Data
    public class Pen {
        private String type; // 笔类型
        private String color;
    }
    
    @Data
    public class Bag implements Cloneable {
    
        private String name;
        private String color;
        private Pen pen;
    
        @Override
        protected Object clone() throws CloneNotSupportedException {
            return super.clone();
        }
    }
    
    @Data
    public class Student implements Cloneable {
    
        private String name;
        private int age;
        private Bag bag;
    
        @Override
        public Object clone() throws CloneNotSupportedException {
            Student student = (Student) super.clone();
            student.setBag((Bag) this.bag.clone()); // bag 需要实现 Cloneable
            return student;
        }
    }
    

    测试代码:

    @Slf4j
    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
            log.info("----------------- 深拷贝测试1 ------------------");
            Student student2 = (Student) student1.clone();
            log.info("student1 == student2: " + (student1 == student2)); // false, 不是同一个地址
            log.info("student1.bag == student2.bag: " + (student1.getBag() == student2.getBag())); // false 深拷贝, 不同引用
    
            // 特别注意: 下面为true, 虽然是深拷贝, 但深拷贝对象的复合数据类型仍可能含有复合数据类型,导致完全深拷贝失败, 由此我们可以发现:
            // clone() 深拷贝不适合嵌套对象, 特别是嵌套类型很多的情况, 因为对这些类实现深拷贝, 每一个类都需要重写Cloneable.clone()方法
            // 因此我们可以选择另外一种方式实现深拷贝————序列化
            log.info("注意: student1.bag.pen == student2.bag.pen:" + (student1.getBag().getPen() == student2.getBag().getPen()));
    
            log.info(JSONObject.toJSONString(student1));
            log.info(JSONObject.toJSONString(student2));
    
            log.info("----------------- 深拷贝测试2 ------------------");
            pen.setType("钢笔");
            pen.setColor("红色");
            log.info(JSONObject.toJSONString(student1));
            log.info(JSONObject.toJSONString(student2));
            log.info("pen是否指向同一个:" + (student1.getBag().getPen() == student2.getBag().getPen()));
        }
    }
    

    测试结果:

    ----------------- 深拷贝测试1 ------------------
     student1 == student2: false
     student1.bag == student2.bag: false
     注意: student1.bag.pen == student2.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

    image.png
    既然发现了问题,那么要实现完全的深拷贝就简单了
    a).将Pen.java也实现cloneable接口,并且重写clone()
    b).在Bag.java中的clone()方法使用pen.clone()深拷贝一份到bag中的pen成员中

    Question
    如果Pen中又有一个复杂数据类型呢?岂不是又要将Pen中的复杂数据类型也要做同样的操作?

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

    03
    深拷贝——序列化

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

    @Data
    public class Pen implements Serializable {
        private String type; // 笔类型
        private String color;
        // 省略get/set
    }
    
    @Data
    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;
        }
    
    }
    

    测试代码:

    @Slf4j
    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 (每个子类)
            log.info("----------------- 序列化-深拷贝测试1 ------------------");
            // Student student2 = (Student) student1.deepClone(); // 方法一
            Student student2 = CloneUtils.deepClone(student1); // 方法二: 使用工具
    
            log.info("stu1 == stu2: " + (student1 == student2));
            log.info("stu1.bag == stu2.bag: " + (student1.getBag() == student2.getBag()));
            log.info("stu1.bag.pen == stu2.bag.pen: " + (student1.getBag().getPen() == student2.getBag().getPen()));
    
            log.info(JSONObject.toJSONString(student1));
            log.info(JSONObject.toJSONString(student2));
    
            log.info("----------------- 序列化-深拷贝测试2 ------------------");
            pen.setType("钢笔");
            pen.setColor("红色");
            log.info(JSONObject.toJSONString(student1));
            log.info(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":"张小凡"}
    

    image.png 总结:
    01
    浅拷贝

    假如 Student(含有非基本数据类型) 实现了 Cloneable , 重写了 clone()
    那么 Student2 student2=(Stundet) student1.clone(); 就是一个浅拷贝
    浅拷贝特点:
     1.对象本身是新对象
     2.对象里面的基本数据会复制, 基本数据不存在引用;
        特殊的String类型,有深拷贝表现;
        String 存在于堆内存、常量池;这种比较特殊, 本身没有实现 Cloneable, 传递是引用地址;
        由本身的final性, 每次赋值都是一个新的引用地址,原对象的引用和副本的引用互不影响。
        因此String就和基本数据类型一样,表现出了"深拷贝"特性.
     3.对象里面的复杂数据类型会进行浅拷贝, 指向的同一个引用地址
    

    02
    深拷贝

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

    03
    序列化

    不需要递归让所有对象实现cloneable接口, 方便简洁;
    

    image.png