一篇文章带你了解cloneable接口、浅拷贝、深拷贝
作者:鱼仔
博客首页: https://codeease.top
公众号:Java鱼仔
# (一)cloneable接口有什么用
我们都知道想要实现拷贝需要实现Cloneable接口并在类中实现clone()方法,不过比较神奇的是,clone()方法并不是Cloneable接口中的方法。
Cloneable接口是一个空接口,里面没有任何内容
但是如果没有实现Cloneable接口,就会导致clone()方法报CloneNotSupportException错误,所以你可以把Cloneable接口看成实现clone()方法必须要的一个因素。
# (二)什么是深拷贝和浅拷贝
开发过程中,有时会遇到把现有的一个对象的所有成员属性拷贝给另一个对象的需求。这个时候就会用到拷贝这个概念。我们把原对象定义成A,拷贝后的对象定义成B,如果只是单纯使用clone方法进行拷贝,你会发现:
1、对于八个基本类型,会拷贝其值,并且B的改变不会影响A。
2、如果是一个对象,拷贝的是地址引用,也就是说此时新拷贝出的对象与原有对象共享该实例变量,不受访问权限的限制。B对该值的改变会影响A。
3、对于String字符串,这个比较特殊,虽然拷贝的也是引用,但是在修改的时候,它会从字符串池中重新生成新的字符串,原有的字符串对象保持不变。
这种只单纯拷贝引用地址的动作就是浅拷贝。
相反,如果拷贝一个对象时不是简单的将地址引用拷贝出来,而是新建了一个对象,这种方式就是深拷贝。
# (三)浅拷贝代码模拟
通过代码模拟浅拷贝的过程:
首先,新建两个实体类,学生和老师:
public class Teacher {
private int id;
private String name;
//省略构造方法、get、set、toString方法
}
2
3
4
5
接下来是学生,学生的实体类需要实现clone
public class Student implements Cloneable {
private int id;
private String name;
private Teacher teacher;
//省略构造方法、get、set、toString方法
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
2
3
4
5
6
7
8
9
10
接下来就来看一下浅拷贝的效果
public static void main(String[] args) throws CloneNotSupportedException {
//新建一个student1
Student student1=new Student(1,"javayz",new Teacher(1,"teacher1"));
//student2从student1中克隆过去
Student student2= (Student) student1.clone();
//修改基本类型 int
student2.setId(2);
//修改String
student2.setName("javayz2");
//修改对象类型teacher
Teacher teacher = student2.getTeacher();
teacher.setName("teacher2");
System.out.println(student1);
System.out.println(student2);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
通过结果就可以发现,修改被克隆对象的基本类型和String类型不会对原来数据造成影响,但是由于用的是同一个引用地址,修改对象时两边都会被修改。
# (四)深拷贝代码模拟
深拷贝的其中一个方法是把被拷贝对象中的所有引用类型也都实现深拷贝,最后逐层拷贝实现引用地址是新的而不是用的同一个。
修改上面的teacher对象代码,实现clone方法
public class Teacher implements Cloneable{
private int id;
private String name;
//省略构造方法、get、set、toString方法
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
2
3
4
5
6
7
8
9
修改student类的clone方法
@Override
protected Object clone() throws CloneNotSupportedException {
Student student = (Student) super.clone();
student.teacher= (Teacher) teacher.clone();
return student;
}
2
3
4
5
6
然后执行同样的测试代码后就会发现两个对象已经互相不影响了。
第二个方法是利用serializable实现深拷贝,这种方式的原理在于通过IO流的方式先将序列化后的对象写进IO流中,再取出来实现深拷贝。这种方式下所有涉及到的类都必须实现Serializable接口
新建一个DeepStudent类
public class DeepStudent implements Serializable {
private static final long serialVersionUID=1L;
private int id;
private String name;
private Teacher teacher;
//省略构造方法、get、set、toString方法
public Object deepCopy(){
try {
//将对象写到IO流中
ByteArrayOutputStream bos=new ByteArrayOutputStream();
ObjectOutputStream oos=new ObjectOutputStream(bos);
oos.writeObject(this);
//再从IO流中获取到对象
ByteArrayInputStream bis=new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois=new ObjectInputStream(bis);
return ois.readObject();
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
编写一个测试方法:
public static void main(String[] args) throws CloneNotSupportedException {
//新建一个student1
DeepStudent student1=new DeepStudent(1,"javayz",new Teacher(1,"teacher1"));
//student2从student1中克隆过去
DeepStudent student2= (DeepStudent) student1.deepCopy();
//修改基本类型 int
student2.setId(2);
//修改String
student2.setName("javayz2");
//修改对象类型teacher
Teacher teacher = student2.getTeacher();
teacher.setName("teacher2");
System.out.println(student1);
System.out.println(student2);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15