Java实现克隆需要遵循以下规则:

  1. 必须实现Cloneable接口
  2. 实现Cloneable的类应该重写clone(),重写时该方法的修饰符为public。
public class CloneTest {
    public static void main(String[] args) {
        Student stu = new Student();
        stu.setName("张三");
        stu.setAge(10);
        Classes classes = new Classes();
        classes.setClassId(101);
        classes.setClassName("一班");
        stu.setClasses(classes);
        try {
            System.out.println("浅克隆测试------"); //克隆
            Student stu2 = (Student) stu.clone();
            System.out.println("两个对象是否相同:" + (stu == stu2));
            System.out.println("两个对象的name属性是否相同:" + (stu.getName() == stu2.getName()));
            System.out.println("两个对象的classes属性是否相同:" + (stu.getClasses() == stu2.getClasses()));
            System.out.println("浅克隆,Stu " + stu);
            System.out.println("浅克隆,Stu2 " + stu);
            System.out.println("修改克隆对象属性");
            stu2.setName("李四");//修改姓名
            stu2.setAge(20);//修改年龄
            stu2.getClasses().setClassId(102);//修改班级编号
            stu2.getClasses().setClassName("二班");//修改班级名称
            System.out.println("修改克隆对象属性后,Stu " + stu);
            System.out.println("修改克隆对象属性后,Stu2 " + stu2);
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
    }


}

@Data
class Classes{
    private int classId;
    //基本类型
    private String className;//引用类型 //这里省略gettters and setters

    @Override
    public String toString() {
        return "Classes [classId=" + classId + ", className=" + className + "]";
    }
//    @Override
//    public Object clone() throws CloneNotSupportedException {
//        return super.clone();
//    }
}

@Data
class Student implements Cloneable {
    private String name;//引用类型
    private int age;//基本类型
    private Classes classes;//引用类型 //这里省略gettters and setters

    @Override
    public String toString() {
        return "Student [name=" + name + ", age=" + age + ", classes=" + classes + "]";
    }

    @Override
    public Object clone() throws CloneNotSupportedException {
        Student stu = (Student) super.clone();
//        Classes cla = (Classes)classes.clone();
//        stu.setClasses(cla);
        return stu;
    }
}
结果:
浅克隆测试------
两个对象是否相同:false
两个对象的name属性是否相同:true
两个对象的classes属性是否相同:true
浅克隆,Stu Student [name=张三, age=10, classes=Classes [classId=101, className=一班]]
浅克隆,Stu2 Student [name=张三, age=10, classes=Classes [classId=101, className=一班]]
修改克隆对象属性
修改克隆对象属性后,Stu Student [name=张三, age=10, classes=Classes [classId=102, className=二班]]
修改克隆对象属性后,Stu2 Student [name=李四, age=20, classes=Classes [classId=102, className=二班]]

通过Debug观察属性和运行测试类得到的结果,可以发现原始对象stu和克隆对象stu2不是同一对象,但是两个对象的name属性和classes属性是同一个对象,并且原始对象stu和克隆对象stu2的属性值是相等的,这也验证了“Java的克隆机制是对类的实例的属性逐一复制”。name属性和classes同为引用类型的实例,克隆后原始对象stu和克隆对象stu2的name属性和classes属性是同一对象,说明没有实现Cloneable接口的类的实例克隆是通过传引用实现的。

修改了克隆对象stu2的各个属性后,可以发现:原始对象stu的classes属性跟随克隆对象stu2的classes属性变化了,但name和age属性没有跟随变化,这是为什么呢?

age属性没有跟随变化比较容易理解,因为age属性是int类型,基本类型克隆时只是传值,不存在传引用。

同样是传引用,为什么name属性没有跟随变化,难道name属性有特别之处?name属性是String类型,String类型也没有实现Conleable啊,很是困惑。

回忆下String类的声明,public final class String implements …,final关键字是不是很可疑呢?再想想final关键字的作用,一是不可变,二是禁止指令重排。不可变,不可变,是不是想到什么了?正是因为String不可变的特性,克隆对象stu2并没有修改name属性的值,而是修改了name属性的引用。这就是为什么name属性没有跟随克隆对象的变化而变化,String不可变好像让浅克隆变为了深克隆。

public class CloneTest {
    public static void main(String[] args) {
        Student stu = new Student();
        stu.setName("张三");
        stu.setAge(10);
        Classes classes = new Classes();
        classes.setClassId(101);
        classes.setClassName("一班");
        stu.setClasses(classes);
        try {
            System.out.println("浅克隆测试------"); //克隆
            Student stu2 = (Student) stu.clone();
            System.out.println("两个对象是否相同:" + (stu == stu2));
            System.out.println("两个对象的name属性是否相同:" + (stu.getName() == stu2.getName()));
            System.out.println("两个对象的classes属性是否相同:" + (stu.getClasses() == stu2.getClasses()));
            System.out.println("浅克隆,Stu " + stu);
            System.out.println("浅克隆,Stu2 " + stu);
            System.out.println("修改克隆对象属性");
            stu2.setName("李四");//修改姓名
            stu2.setAge(20);//修改年龄
            stu2.getClasses().setClassId(102);//修改班级编号
            stu2.getClasses().setClassName("二班");//修改班级名称
            System.out.println("修改克隆对象属性后,Stu " + stu);
            System.out.println("修改克隆对象属性后,Stu2 " + stu2);
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
    }


}

@Data
class Classes implements Cloneable{
    private int classId;
    //基本类型
    private String className;//引用类型 //这里省略gettters and setters

    @Override
    public String toString() {
        return "Classes [classId=" + classId + ", className=" + className + "]";
    }
    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

@Data
class Student implements Cloneable {
    private String name;//引用类型
    private int age;//基本类型
    private Classes classes;//引用类型 //这里省略gettters and setters

    @Override
    public String toString() {
        return "Student [name=" + name + ", age=" + age + ", classes=" + classes + "]";
    }

    @Override
    public Object clone() throws CloneNotSupportedException {
        Student stu = (Student) super.clone();
        Classes cla = (Classes)classes.clone();
        stu.setClasses(cla);
        return stu;
    }
}

深克隆测试------
两个对象是否相同:false
两个对象的name属性是否相同:true
两个对象的classes属性是否相同:false
深克隆,Stu Student [name=张三, age=10, classes=Classes [classId=101, className=一班]]
深克隆,Stu2 Student [name=张三, age=10, classes=Classes [classId=101, className=一班]]
修改克隆对象属性
修改克隆对象属性后,Stu Student [name=张三, age=10, classes=Classes [classId=101, className=一班]]
修改克隆对象属性后,Stu2 Student [name=李四, age=20, classes=Classes [classId=102, className=二班]]

通过Debug观察属性和运行测试类得到的结果,克隆后原始对象stu和克隆对象stu2的classes属性不是同一个对象。修改了克隆对象stu2的各个属性后,可以发现:原始对象stu的属性不再跟随克隆对象stu2的属性变化而变化。

通过对Java克隆机制的学习和检验,我们得出一个结论:如果要对某个类的的实例的引用类型属性实现深克隆,要求引用类型属性对于的类实现Cloneable接口并重写clone()。