java深浅 clone_浅谈关于java中的深浅拷贝

一.浅拷贝(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对应的对象不在同一块内存空间,结果如下:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

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);

结果如下:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

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()));

}

运行结果如下:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

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:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

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 都会影响另外一个;

通过图会更直观一些:

453cac616b7ae2ce0dab03be7111daf7.png

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:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

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也进行了修改,如图:

da8941a1efed223fa967fe9d5a738b3f.png

目前来看是满足了我们的需求,但是如果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接口的情况下,运行结果如下:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

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方式

如何抉择?

实际开发中,根据两种方式的优缺点进行选择即可!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值