原文链接:https://blog.csdn.net/zhaoheng314/article/details/81985880
Java实现克隆需要遵循以下规则:
- 必须实现Cloneable接口
- 实现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()。