开发过程中,有时会遇到把现有的一个对象的所有成员属性拷贝给另一个对象的需求。这个时候就会用到拷贝这个概念。把原对象定义成 A,拷贝后的对象定义成 B,如果只是单纯使用 clone 方法进行拷贝,你会发现:
-
对于八个基本类型,会拷贝其值,并且 B 的改变不会影响 A。
-
如果是一个对象,拷贝的是地址引用,也就是说此时新拷贝出的对象与原有对象共享该实例变量,不受访问权限的限制。B 对该值的改变会影响 A。
-
对于 String字符串,这个比较特殊,虽然拷贝的也是引用,但是在修改的时候,它会从字符串池中重新生成新的字符串,原有的字符串对象保持不变。
这种只单纯拷贝引用地址的动作就是浅拷贝。
相反,如果拷贝一个对象时不是简单的将地址引用拷贝出来,而是新建了一个对象,这种方式就是深拷贝。
浅拷贝代码模拟
通过代码模拟浅拷贝的过程:
首先,新建两个实体类,学生和老师:
public class Teacher {
private int id;
private String name;
public Teacher(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
public void setId(int id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Teacher{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
接下来是学生,学生的实体类需要实现 clone
public class Student implements Cloneable {
private int id;
private String name;
private Teacher teacher;
public Student(int id, String name, Teacher teacher) {
this.id = id;
this.name = name;
this.teacher = teacher;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Teacher getTeacher() {
return teacher;
}
public void setTeacher(Teacher teacher) {
this.teacher = teacher;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", teacher=" + teacher +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
接下来就来看一下浅拷贝的效果
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); //{id=1,name='javayz',teacher=Teacher{id=1,name='teacher2'}}
System.out.println(student2); //{id=2,name='javayz2',teacher=Teacher{id=1,name='teacher2'}}
}
通过结果就可以发现,修改被克隆对象的基本类型和 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();
}
}
修改 student 类的 clone 方法
@Override
protected Object clone() throws CloneNotSupportedException {
Student student = (Student) super.clone();
student.teacher= (Teacher) teacher.clone();
return student;
}
然后执行同样的测试代码后就会发现两个对象已经互相不影响了。
第二个方法是利用 serializable 实现深拷贝,这种方式的原理在于通过 IO 流的方式先将序列化后的对象写进 IO 流中,再取出来实现深拷贝。这种方式下所有涉及到的类都必须实现 Serializable 接口
新建一个 DeepStudent 类,同时需要使得 Teacher 类也实现 Serializable 接口
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;
}
}
编写一个测试方法:
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);
}