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()。