Java中克隆clone()与赋值引用的区别及实现
在Java开发中,理解对象克隆(clone)与普通引用赋值的区别是编写健壮代码的基础。很多开发者容易混淆两者,导致在数据修改时出现意想不到的副作用。本文将通过具体的学生类(Student)代码案例,详细演示如何实现对象克隆,并对比其与直接赋值引用在内存和属性变更上的不同表现。
核心概念:引用赋值 vs 对象克隆
Java中的对象赋值(如 Student s3 = s;)仅仅是将堆内存中对象的地址赋予新的变量。这意味着 s 和 s3 指向同一个内存实体,任何一方的属性修改都会直接影响另一方。
而对象克隆则是创建了一个全新的对象副本,拥有独立的内存空间。修改克隆对象不会影响原始对象。要实现这一功能,需要涉及以下几个关键步骤:
- 实现
Cloneable接口:这是一个标记接口,用于告知JVM该对象允许被克隆。 - 重写
clone()方法:从protected改为public或保持保护级别但通过包装调用,以公开克隆能力。 - 处理
CloneNotSupportedException:虽然实现了接口,但语法上仍需声明可能抛出的异常。
实践操作:实现与测试克隆
第1步:定义支持克隆的学生类
首先,我们需要创建一个基础的学生类。为了实现克隆,必须实现 java.lang.Cloneable 接口。注意,这个接口没有任何方法,它仅作为一个“通行证”。
package 克隆clone;
/*要克隆必须实现这个借口:Cloneable,以标记这个对象可以克隆
Cloneable:此类实现了 Cloneable 接口,以指示 Object.clone() 方法可以合法地对该类实例进行按字段复制。
这个接口是标记接口,告诉我们实现该接口的类就可以实现对象的复制了。
*/
public class Student implements Cloneable {
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
// 重写克隆方法子列才可以调用
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
第2步:编写测试代码验证差异
接下来,我们创建一个测试类 StudentClone 来对比两种方式的实际效果。我们将观察在对新变量进行修改时,原始对象的变化情况。
package 克隆clone;
/*
* protected void finalize():当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。用于垃圾回收,但是什么时候回收不确定。
* protected Object clone():创建并返回此对象的一个副本。
* A:重写该方法
*
* Cloneable:此类实现了 Cloneable 接口,以指示 Object.clone() 方法可以合法地对该类实例进行按字段复制。
* 这个接口是标记接口,告诉我们实现该接口的类就可以实现对象的复制了。
*/
public class StudentClone {
public static void main(String[] args) throws CloneNotSupportedException {
//创建学生对象
Student s = new Student();
s.setName("林青霞");
s.setAge(27);
//克隆学生对象
Object obj = s.clone();
Student s2 = (Student)obj;
System.out.println("---------");
System.out.println(s.getName()+"---"+s.getAge());
System.out.println(s2.getName()+"---"+s2.getAge());
//以前的做法:直接赋值引用
Student s3 = s;
System.out.println(s3.getName()+"---"+s3.getAge());
System.out.println("---------");
//其实是有区别的
//修改引用指向的对象
s3.setName("刘意");
s3.setAge(30);
System.out.println(s.getName()+"---"+s.getAge());
System.out.println(s2.getName()+"---"+s2.getAge());
System.out.println(s3.getName()+"---"+s3.getAge());
System.out.println("---------");
//把克隆的改一下呢?
//修改克隆的对象
s2.setName("克隆改的名称");
s2.setAge(66);
System.out.println(s.getName()+"---"+s.getAge());
System.out.println(s2.getName()+"---"+s2.getAge());
System.out.println(s3.getName()+"---"+s3.getAge());
//也就是说把一个对象的引用赋值给一个引用,当这个引用发什么该改变的时候,同一个引用指向的对象会发生改变,但是克隆的不会变化,
//把克隆的对象改变他的一些属性,那么也不会影响到其他的对象的值的改变
}
}
结果分析与排错指南
通过上述代码的运行结果,我们可以得出以下明确结论:
- 引用赋值(Shallow Reference):当执行
Student s3 = s;后,s3和s指向同一块内存。后续通过s3修改姓名和年龄,s的内容也随之改变。这在数据不需要隔离的场景下可能方便,但在需要备份或防止副作用时必须避免。 - 对象克隆(Clone):执行
s.clone()后得到的s2是一个独立的新对象。即使后续大幅修改s2的属性(如改名改年龄),原始的s对象内容保持不变。这证明了克隆成功建立了内存隔离。
注意事项:
- 浅拷贝问题:上述示例中的
Student只包含基本数据类型(int)和不可变字符串(String)。如果类中包含可变对象引用(如自定义类数组或HashMap),默认的super.clone()执行的是浅拷贝,即克隆对象内的引用仍指向原对象里的内部对象。如需深拷贝,需手动递归克隆成员变量。 - 线程安全:在多线程环境下,直接使用
clone()可能存在风险,建议配合锁或使用Serialization方式进行深拷贝。
总结
Java中的 clone() 方法是实现对象独立副本的关键机制,区别于简单的引用赋值。掌握 Cloneable 接口的使用以及重写 clone 方法的正确姿势,能够有效避免因数据共享导致的逻辑错误。以上就是Java中的克隆clone()方法和赋值引用的详细区别的全部内容,更多关于Java对象内存管理的资料请关注本站其它相关文章!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













