本文详细介绍了Java中的浅克隆和深克隆的概念,及案例演示如何实现深克隆!
1 克隆概述
Java中实现对象的克隆分两种一种是浅克隆一种是深克隆。首先java中Clone方法对于对象克隆的机制是:对象的基本数据类型的成员变量会被全部复制,引用类型的成员变量不会复制,只会复制该变量的引用,这样被克隆对象的引用类型的成员变量还是指向了原对象的同名引用类型的成员变量的堆内存空间,对其中一个对象的引用类型成员变量的修改会影响到另外一个被克隆对象或者源对象的引用类型的成员变量。
浅克隆:最普遍的克隆,即对象实现cloneable接口和重写clone方法,然后调用一次内部不做改写的clone方法克隆出一个对象,如果源对象内部存在引用类型的成员变量,那么就说该克隆是浅克隆,即对于引用类型属性,只克隆引用,两个对象的引用指向同一块内存地址,即同一个对象。
深克隆:基本数据类型变量和引用类型变量指向的对象都会被复制,即针对引用类型的成员变量真正的复制一份,重新开辟空间保存,这样两个引用类型属性互不影响。
2 深克隆实现
实现深克隆的方法有三种:
-
重写clone方法,clone中嵌套clone
- 这种方法的原理其实就是在需要克隆的对象以及该对象的引用类型的变量的类中全部实现cloneable接口,否则抛出CloneNotSupportedException将引用类型的变量也克隆一份。实际的操作上就是改写源对象的clone方法,在其内部嵌套克隆方法。
首先让源对象调用克隆方法获得克隆的对象,然后获得被克隆对象的引用类型的成员变量对象,对该成员变量对象调用克隆方法,此时成员变量对象也被克隆了一份,最后将该克隆的成员变量对象,设置为克隆对象的新的成员变量,再返回该被克隆的对象,即可实现深克隆。
- 这种方法的原理其实就是在需要克隆的对象以及该对象的引用类型的变量的类中全部实现cloneable接口,否则抛出CloneNotSupportedException将引用类型的变量也克隆一份。实际的操作上就是改写源对象的clone方法,在其内部嵌套克隆方法。
-
使用序列化流
- 其原理是:首先使要序列化的对象和该对象的引用类型成员变量对象的类都实现Serializable接口,将对象序列化到输出流中,然后再反序列化为对象就完成了完全的复制操作了,反序列化对象类似于运行新对象的构造方法。一般序列化到外部文件,此时只需要克隆对象,并不需要外部文件,因此我们的序列化和反序列化也应该在内存中进行最好,因此还使用到在内存操作数据的流ByteArrayOutputStream和ByteArrayInputStream,他们的输出和读取都默认是在内存的数组中操作。
- 首先创建一个ByteArrayOutputStream内存数组输出流,创建一个ObjectOutputStream序列化流,并传入内存数组输出流,使用序列化流的writeobject方法将要序列化的对象写入内部数组中,然后创建一个ByteArrayInputStream内存数组读取流,传入一个读取数据的数组,这个数组通过内存数组输出流的toByteArray方法获得,这个数组里面的数据其实就是已经被序列化成二进制数据的对象。最后创建一个ObjectInputStream反序列化流,并传入内存数组读取流,使用反序列化流的readobject方法将数组中的对象的信息,反序列化出来。反序列化出的对象就是一个新的对象,完成了深克隆。当然还可以固定要被序列化对象的版本号,定义一个private static final long serialVersionUID,但需要注意静态的成员和transient关键字修饰的成员不能被序列化。
-
使用开源工具类
- 比如Json工具类(先转换为Json字符串,然后再转换为对象)、Spring的BeanUtils(Spring项目中使用比较方便)、Cglib的BeanCopier(速度最快)、Apache的BeanUtils(该工具类比较慢,谨慎使用!)
3 案例
这里为了方便,没有使用get、set方法。
3.1 测试普通clone方法–浅克隆
public class Teacher implements Cloneable {
private String name;
private int age;
private Student stu;
public static void main(String[] args) throws CloneNotSupportedException {
Student stu = new Student("李四", 24);
Teacher tea = new Teacher("张三", 30, stu);
//使用未做改变的clone方法
Teacher teaClone = (Teacher) tea.clone();
/*clone之后改变原对象的数据*/
//改变stu的数据
stu.name="李四改";
//改变tea的数据
tea.name="张三改";
//结果被克隆的数据的内部类的stu数据也受到了影响,说明未重写的clone方法,实现的只是浅克隆,tea的对象类型属性stu还是指同一个对象
System.out.println(teaClone);
System.out.println(tea);
}
public Teacher(String name, int age, Student stu) {
super();
this.name = name;
this.age = age;
this.stu = stu;
}
public static class Student implements Cloneable {
private String name;
private int age;
public Student(String name, int age) {
super();
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
@Override
public String toString() {
return "Teacher{" +
"name='" + name + '\'' +
", age=" + age +
", stu=" + stu +
'}';
}
}
3.2 使用重写后的clone方法–深克隆
先让外部类和内部类都重写clone方法,然后改写clone方法:
public class Teacher implements Cloneable {
private String name;
private int age;
private Student stu;
public static void main(String[] args) throws CloneNotSupportedException {
Student stu = new Student("李四", 24);
Teacher tea = new Teacher("张三", 30, stu);
//使用重写的的clone方法
Teacher teaClone = (Teacher) tea.clone();
/*clone之后改变原对象的数据*/
//改变stu的数据
stu.name="李四改";
//改变tea的数据
tea.name="张三改";
//结果被克隆的数据的内部类的stu数据没受到了影响,说明重写的clone方法,实现的是深克隆,tea的对象类型属性stu是指不同对象
System.out.println(teaClone);
System.out.println(tea);
}
@Override
protected Object clone() throws CloneNotSupportedException {
//改写clone方法
Teacher tea = (Teacher) super.clone();
//获取属性对象,再clone一次,让后设置到被克隆的对象中,返回
tea.stu = ((Student) tea.stu.clone());
return tea;
}
public Teacher(String name, int age, Student stu) {
super();
this.name = name;
this.age = age;
this.stu = stu;
}
public static class Student implements Cloneable {
private String name;
private int age;
public Student(String name, int age) {
super();
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
@Override
public String toString() {
return "Teacher{" +
"name='" + name + '\'' +
", age=" + age +
", stu=" + stu +
'}';
}
}
这种方法对于不能重写的类,比如数组不适用!
3.3 使用序列化流–深克隆
使得两个类实现Serialzable接口,clone方法可以不要了,使用序列化流。
public class Teacher implements Serializable {
private String name;
private int age;
private Student stu;
public static void main(String[] args) throws IOException, ClassNotFoundException {
Student stu = new Student("李四", 24);
Teacher tea = new Teacher("张三", 30, stu);
//内存数组输出流
ByteArrayOutputStream bao = new ByteArrayOutputStream();
//序列化流
ObjectOutputStream oos = new ObjectOutputStream(bao);
//将数据tea写入序列化流中,随后会被传递到内存数组输出流中,将对象序列化为byte[]类型的数据
oos.writeObject(tea);
//从内存数组输出流中获取到tea的byte[]类型的数据,传入内存数组输入流
ByteArrayInputStream bai = new ByteArrayInputStream(bao.toByteArray());
//将内存数组输入流传给反序列化流,这样也实现了byte[]类型的数据的传递
ObjectInputStream ois = new ObjectInputStream(bai);
//使用readObject,从反序列化流中读取数据,将byte[]类型的数据反序列化成Teacher对象
Teacher teaClone = (Teacher) ois.readObject();
//改变stu的数据
stu.name = "李四改";
//改变tea的数据
tea.name = "张三该";
//结果被克隆的数据的内部类的stu数据没有受到了影响,说明重写后的clone方法,实现了深克隆
System.out.println(teaClone);
System.out.println(tea);
}
public Teacher(String name, int age, Student stu) {
super();
this.name = name;
this.age = age;
this.stu = stu;
}
public static class Student implements Serializable {
private String name;
private int age;
public Student(String name, int age) {
super();
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
@Override
public String toString() {
return "Teacher{" +
"name='" + name + '\'' +
", age=" + age +
", stu=" + stu +
'}';
}
}
3.4 使用开源工具
这里只介绍Json工具:Gson的使用。
//maven依赖
<!-- https://mvnrepository.com/artifact/com.google.code.gson/gson -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.5</version>
</dependency>
也可以使用jar包形式,引入Gson工具:Gson jar包下载
public class Teacher {
private String name;
private int age;
private Student stu;
public static void main(String[] args) {
Student stu = new Student("李四", 24);
Teacher tea = new Teacher("张三", 30, stu);
/*使用Gson工具*/
Gson gson = new Gson();
//将对象序列化为json字符串
String teaStr = gson.toJson(tea);
//然后将字符串反序列化为对象
Teacher GsonTea = gson.fromJson(teaStr, Teacher.class);
/*clone之后改变原对象的数据*/
//改变stu的数据
stu.name = "李四改";
//改变tea的数据
tea.name = "张三改";
/*结果被克隆的数据的内部类的stu数据没受到了影响,说明使用JSON工具,实现的是深克隆,tea的对象类型属性stu不是指向同一个对象*/
System.out.println(GsonTea);
System.out.println(tea);
}
public Teacher(String name, int age, Student stu) {
super();
this.name = name;
this.age = age;
this.stu = stu;
}
public static class Student {
private String name;
private int age;
public Student(String name, int age) {
super();
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
@Override
public String toString() {
return "Teacher{" +
"name='" + name + '\'' +
", age=" + age +
", stu=" + stu +
'}';
}
}
从结果来看,我们并没有实现Cloneable和Serializable接口,但是对结果并没有影响。