深克隆与浅克隆

出现深克隆和浅克隆这两种情况,是因为java中分为基础数据类型和引用数据类型,对于不同的数据类型在内存中存储的区域是不同的,基本数据类型存储在栈中,引用数据类型存储在堆中。

1、什么是克隆

克隆就是根据已有数据,创建出一份完全一样的新的数据拷贝。

实现克隆有多种方式,可以手工的new出一个新的对象,然后将原来的对象信息一个一个的set到新的对象中。还有就是使用clone方法。使用clone方法必须满足:

  1. 实现Cloneable接口
  2. 使用public访问修饰符重新定义clone方法。

2、浅克隆

要实现浅克隆被克隆的类必须实现Cloneable接口
(未实现接口会跑出CloneNotSupportedException异常)

定义:被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。

3、深克隆

两种实现方法:

  1. 要实现深克隆被克隆类以及被克隆类的引用必须实现Serializable接口(未实现接口会抛出NotSerializableException异常)
  2. 每个属性都重写clone方法

属性少的时候可以选择重写clone方法, 属性多的时候应该选择序列化方法。

定义:被复制对象的所有变量都含有与原来的对象相同的值,除去那些引用其他对象的变量。那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象。深克隆的是实现实际上是对对象的序列化和反序列化

4、代码示例

实例类:

public class Student implements Cloneable,Serializable {
    private String name;
    private int age;
    private Date birthday;
    private List<String> grades;
    public Student(String name, int age,  Date birthday, List<String> grades) {
        super();
        this.name = name;
        this.age = age;
        this.birthday = birthday;
        this.grades = grades;
    }
    ..........
    //省略get set方法

    @Override
    public String toString() {
        return "Student [name=" + name + ", age=" + age + ", birthday="
                + birthday + ",grades="+grades+"]";
    }
    /**
     * 完成浅克隆类需实现Cloneable接口
     * @param student
     * @return Object
     */
    public Object shallowClone(Student student){
        try {
            return student.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return null;
    }
    /**
     * 完成深克隆被克隆的类和类的引用类
     * 均实现Serializable接口
     * @param student
     * @return Object
     */
    public Object deepClone(Student student){
        /*
         *本次实现深克隆使用 ByteArrayOutputStream
         * 和ByteArrayInputStream
         *作为复制过程中字符数组存储中介
         */
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos =null;
        ObjectInputStream ois = null;
        try {
            //将bos作为收集字节数组中介
            oos= new ObjectOutputStream(bos);
            //将传入参数student类写入bos中
            oos.writeObject(student);
            //将读取到数据传入ObjectInputStream
            ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
            return ois.readObject();
        } catch (IOException | ClassNotFoundException e) {
            //JDK 1.7后引入 可以同时用| 优化代码可读性
            e.printStackTrace();
            return null;
        } finally{
            try {
                bos.close();
                oos.close();
                ois.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

}

测试类:

public class Test {
    public static void main(String[] args) {
        Date date = new Date();
        List<String> grades = new ArrayList<>();
        grades.add("英语100分");
        //创建原型类
        Student student = new Student("小强", 21, date, grades);
        System.out.println("原student类 :     " + student);
        //浅克隆对象shallowCloneStudent
        Student shallowClone= (Student) student.shallowClone(student);
        System.out.println("浅克隆出来的student: " + shallowCloneStudent);
        //修改引用数据类型,看看浅克隆对象中的有没有被改变
        date.setTime(123123123);
        grades.add("数学120分");
        System.out.println("修改属性后原student :   " + student);
        System.out.println("修改属性后浅克隆student :" + shallowCloneStudent);
        System.out.println("浅克隆和原对象对比   " + shallowClone.equals(student));
        System.out.println("浅克隆Date和原Date类引用是否相同   " + shallowClone.getBirthday().equals(student.getBirthday()));
        //深克隆对象deepClone
        Student deepClone = (Student) student.deepClone(student);
        //修改引用数据类型,看看深克隆对象中的有没有被改变
        date.setTime(new Date().getTime());
        grades.add("语文160分");
        System.out.println("深克隆出来的student   " + deepCloneStudent);
        System.out.println("原来的student:" + student);
        System.out.println("深克隆和深克隆对象对比   " + deepClone.equals(student));
        System.out.println("深克隆Date和深克隆Date类引用是否相同   " + deepClone.getBirthday().equals(student.getBirthday()));

    }
}

输出:

原student类
Student [name=小强, age=21, birthday=Mon Jun 11 10:15:05 CST 2018,grades=[英语100分]]
========================
浅克隆出来的student: 
Student [name=小强, age=21, birthday=Mon Jun 11 10:15:05 CST 2018,grades=[英语100分]]
========================
修改属性后 原student :  改变date和grade之后都变了 
Student [name=小强, age=21, birthday=Fri Jan 02 18:12:03 CST 1970,grades=[英语100分, 数学120分]]
========================
修改属性后 浅克隆student :改变date和grade之后都变了
Student [name=小强, age=21, birthday=Fri Jan 02 18:12:03 CST 1970,grades=[英语100分, 数学120分]]
========================
浅克隆和原对象对比   false
浅克隆Date和原Date类引用是否相同   true
========================
深克隆出来的student      改变date和grade之后没变
Student [name=小强, age=21, birthday=Fri Jan 02 18:12:03 CST 1970,grades=[英语100分, 数学120分]]
========================
原来的student:         改变date和grade之后都变了
Student [name=小强, age=21, birthday=Mon Jun 11 10:26:53 CST 2018,grades=[英语100分, 数学120分, 语文160分]]
========================
深克隆和深克隆对象对比   false
深克隆Date和深克隆Date类引用是否相同   false

从结果可以看到,在浅克隆和深克隆后修改引用数据类型,浅克隆对象shallowClone也随之改变,而深克隆对象deepClone没有被改变。

5、总结

1.浅克隆:只复制基本类型的数据,引用类型的数据只复制了引用的地址,引用的对象并没有复制,在新的对象中修改引用类型的数据会影响原对象中的引用。

2.深克隆:是在引用类型的类中也实现了clone,是clone的嵌套,复制后的对象与原对象之间完全不会影响

3.使用序列化也能完成深复制的功能:对象序列化后写入流中,此时也就不存在引用什么的概念了,再从流中读取,生成新的对象,新对象和原对象之间也是完全互不影响的。

4.使用clone实现的深克隆其实是浅克隆中嵌套了浅克隆,与toString方法类似