一.浅拷贝(shallow copy)
1.如何实现浅拷贝?
Object类 是所有类的直接或间接父类,Object中存在clone方法,如下
protected native Object clone() throws CloneNotSupportedException;
如果想要使一个类的对象能够调用clone方法 ,则需要实现Cloneable接口, 并重写 clone方法:
public class Student implementsCloneable{private intsno ;privateString name;//getter ,setter 省略
@Overridepublic Object clone() throwsCloneNotSupportedException {
Student s= null;try{
s= (Student)super.clone();
}catch(Exception e){
e.printStackTrace();
}returns;
}
}
现在测试clone方法:
@Testpublic void test04() throwsCloneNotSupportedException {//创建Student对象
Student s1 = newStudent();
s1.setSno(1);
s1.setName("Rye");//通过clone 拷贝一个对象
Student s2 =(Student)s1.clone();
System.out.println("s1:"+s1);
System.out.println("s2:"+s2);
System.out.println("s1 == s2 ? ==> "+(s1 ==s2));
}
按照预期,克隆出的对象s2中的字段值应该与s1相同,但与s1对应的对象不在同一块内存空间,结果如下:
s1:Student{sno=1, name='Rye'}
s2:Student{sno=1, name='Rye'}
s1== s2 ? ==> false
View Code
此时如果修改 s1中的sno为2,那么会不会影响到s2中的sno呢?
//修改s1中的sno
s1.setSno(2);
结果如下:
s1:Student{sno=2, name='Rye'}
s2:Student{sno=1, name='Rye'}
View Code
此时看似已经完成了 copy, s1 与 s2有着自己不同的值,但如果为Student中新增了Teacher类型的成员变量,结果还是跟上面一样吗?让我们改造下代码:
public classTeacher {private inttno;privateString name;//getter setter省略...
}
public class Student implementsCloneable{private intsno ;privateString name;privateTeacher teacher;//getter ,setter ,toString 省略...
@Overridepublic Object clone() throwsCloneNotSupportedException {
Student s= null;try{
s= (Student)super.clone();
}catch(Exception e){
e.printStackTrace();
}returns;
}
}
此时测试代码如下:
@Testpublic void test02() throwsCloneNotSupportedException {
Student student1= newStudent();
student1.setSno(1);
student1.setName("Rye");
Teacher teacher= newTeacher();
teacher.setTno(1);
teacher.setName("LinTong");
student1.setTeacher(teacher);
Student student2=(Student)student1.clone();
System.out.println("student1:"+student1);
System.out.println("student2:"+student2);
System.out.println("student1 == student2 ? ==> "+ (student1 ==student2));
System.out.println("student1.teacher == student2.teacher ? ==> "+ (student1.getTeacher() ==student2.getTeacher()));
}
运行结果如下:
student1:Student{sno=1, name='Rye', teacher=Teacher{tno=1, name='LinTong'}}
student2:Student{sno=1, name='Rye', teacher=Teacher{tno=1, name='LinTong'}}
student1== student2 ? ==> falsestudent1.teacher== student2.teacher ? ==> true
View Code
由此可见,此时经过clone生成的student2, 与 student1.二者中的teacher字段, 指向同一块内存空间;
那么可能会有人问,这会有什么影响吗?
我们拷贝的目的,更多的时候是希望获得全新并且值相同的对象,操作原始对象或拷贝的新对象,对彼此之间互不影响;
此时我们修改student1中teacher的tno ,如下:
//修改teacher中的 tno值为2
student1.getTeacher().setTno(2);
再次运行test:
student1:Student{sno=1, name='Rye', teacher=Teacher{tno=2, name='LinTong'}}
student2:Student{sno=1, name='Rye', teacher=Teacher{tno=2, name='LinTong'}}
student1== student2 ? ==> falsestudent1.teacher== student2.teacher ? ==> true
View Code
此时发现,student2中的teacher的tno ,也跟着变化了.
变化的原因是:通过student1执行clone时,基本类型会完全copy一份到student2对应对象内存空间中, 但是对于Teacher对象仅仅是copy了一份Teacher的引用而已.
而student1 与 student2的引用 指向的是同一块堆内存,因此不论是通过student1或是student2修改teacher 都会影响另外一个;
通过图会更直观一些:
2.浅拷贝中引用类型的变量拷贝的是对象的引用 , 可通过如下思路解决:
Teacher类中也覆写clone方法:
@OverrideprotectedObject clone() {
Teacher teacher= null;try{
teacher= (Teacher)super.clone();
}catch(CloneNotSupportedException e) {
e.printStackTrace();
}returnteacher;
}
修改Student中的clone方法,如下:
@OverridepublicObject clone() {
Student s= null;try{
s= (Student)super.clone();
Teacher t= (Teacher)this.teacher.clone();
s.setTeacher(t);
}catch(Exception e){
e.printStackTrace();
}returns;
}
此时再次运行test:
student1:Student{sno=1, name='Rye', teacher=Teacher{tno=2, name='LinTong'}}
student2:Student{sno=1, name='Rye', teacher=Teacher{tno=1, name='LinTong'}}
student1== student2 ? ==> falsestudent1.teacher== student2.teacher ? ==> false
View Code
由此可见,在copy Student的同时 将Teacher也进行了修改,如图:
目前来看是满足了我们的需求,但是如果Teacher类中,同样也有别的引用类型 的成员变量呢?
那么就同样需要一直覆写clone方法,如果这个关系不是特多还可以接受,如果引用关系很复杂就会显得代码繁琐;
此时应该使用序列化完成深度拷贝;
二.深拷贝(deep copy)
使用序列化完成深拷贝
深拷贝是利用对象流,将对象序列化,再反序列化得出新的对象. 因此首先需要实现序列化接口,如下:
public class Student implementsSerializable{private static final long serialVersionUID = -2232725257771333130L;private intsno ;privateString name;privateTeacher teacher;
//getter ,setter,toString()省略...
}
Teacher也要实现序列化接口:
public class Teacher implementsSerializable{private static final long serialVersionUID = 4477679176385287943L;private inttno;privateString name;
//getter ,setter,toString()省略...
}
工具方法:
//工具方法
public Object cloneObject(Object object) throwsIOException, ClassNotFoundException {//将对象序列化
ByteArrayOutputStream outputStream = newByteArrayOutputStream();
ObjectOutputStream objectOutputStream= newObjectOutputStream(outputStream);
objectOutputStream.writeObject(object);//将字节反序列化
ByteArrayInputStream inputStream = newByteArrayInputStream(outputStream.toByteArray());
ObjectInputStream objectInputStream= newObjectInputStream(inputStream);
Object obj=objectInputStream.readObject();returnobj;
}
测试类:
public void test05() throwsIOException, ClassNotFoundException {
Student student1= newStudent();
student1.setSno(1);
student1.setName("Rye");
Teacher teacher= newTeacher();
teacher.setTno(1);
teacher.setName("LinTong");
student1.setTeacher(teacher);
Student student2=(Student)cloneObject(student1);//修改teacher中的 tno值为2
student1.getTeacher().setTno(2);
System.out.println("student1:"+student1);
System.out.println("student2:"+student2);
System.out.println("student1 == student2 ? ==> "+ (student1 ==student2));
System.out.println("student1.teacher == student2.teacher ? ==> "+ (student1.getTeacher() ==student2.getTeacher()));
}
如果Teacher类或者Student类没有实现序列化接口,则执行时会报异常,如下:
java.io.NotSerializableException: com.example.test.Teacher
在都实现了Serializable接口的情况下,运行结果如下:
student1:Student{sno=1, name='Rye', teacher=Teacher{tno=2, name='LinTong'}}
student2:Student{sno=1, name='Rye', teacher=Teacher{tno=1, name='LinTong'}}
student1== student2 ? ==> falsestudent1.teacher== student2.teacher ? ==> false
View Code
由此通过对象流的方式,成功完成了深度拷贝;
三.重写clone方法 与 通过序列化 两种拷贝方式比较:
clone方法:
优点:速度快,效率高
缺点:在对象引用比较深时,使用此方式比较繁琐
通过序列化:
优点:非常简便的就可以完成深度copy
缺点:由于序列化的过程需要跟磁盘打交道,因此效率会低于clone方式
如何抉择?
实际开发中,根据两种方式的优缺点进行选择即可!