java deepcloneobject_Java 基础 - Object.clone()深拷贝和浅拷贝

本文版权归作者所有,欢迎转载,但未经作者同意不能转载,否则保留追究法律责任的权利。

--------------------

总结

1-浅拷贝(shallow copy):创建一个新对象,然后将当前对象的非静态字段复制到该新对象,如果字段是值类型的,那么对该字段执行复制;如果该字段是引用类型的话,则复制引用但不复制引用的对象。因此,原始对象及其副本引用同一个对象。

2-深拷贝 (deep copy):创建一个新对象,然后将当前对象的非静态字段复制到该新对象,无论该字段是值类型的还是引用类型,都复制独立的一份。当你修改其中一个对象的任何内容时,都不会影响另一个对象的内容。

3-如何实现深拷贝?

①、让每个引用类型属性内部都继承Cloneable接口,重写clone() 方法

②、利用序列化。每个需要序列化的类都要实现 Serializable 接口,如果有某个属性不需要序列化,可以将其声明为 transient,即将其排除在克隆属性之外

-----------------------

浅拷贝

我们看如下这段代码:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

packagecom.ys.test;public class Person implementsCloneable{publicString pname;public intpage;publicAddress address;publicPerson() {}public Person(String pname,intpage){this.pname =pname;this.page =page;this.address = newAddress();

}

@Overrideprotected Object clone() throwsCloneNotSupportedException {return super.clone();

}public voidsetAddress(String provices,String city ){

address.setAddress(provices, city);

}public voiddisplay(String name){

System.out.println(name+":"+"pname=" + pname + ", page=" + page +","+address);

}publicString getPname() {returnpname;

}public voidsetPname(String pname) {this.pname =pname;

}public intgetPage() {returnpage;

}public void setPage(intpage) {this.page =page;

}

}

View Code

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

packagecom.ys.test;public classAddress {privateString provices;privateString city;public voidsetAddress(String provices,String city){this.provices =provices;this.city =city;

}

@OverridepublicString toString() {return "Address [provices=" + provices + ", city=" + city + "]";

}

}

View Code

这是一个我们要进行赋值的原始类 Person。下面我们产生一个 Person 对象,并调用其 clone 方法复制一个新的对象。

注意:调用对象的 clone 方法,必须要让类实现 Cloneable 接口,并且覆写 clone 方法。

测试:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

@Testpublic void testShallowClone() throwsException{

Person p1= new Person("zhangsan",21);

p1.setAddress("湖北省", "武汉市");

Person p2=(Person) p1.clone();

System.out.println("p1:"+p1);

System.out.println("p1.getPname:"+p1.getPname().hashCode());

System.out.println("p2:"+p2);

System.out.println("p2.getPname:"+p2.getPname().hashCode());

p1.display("p1");

p2.display("p2");

p2.setAddress("湖北省", "荆州市");

System.out.println("将复制之后的对象地址修改:");

p1.display("p1");

p2.display("p2");

}

View Code

打印结果为:

ee327dac39ee2ae09f34ad4a4224e0e9.png

首先看原始类 Person 实现 Cloneable 接口,并且覆写 clone 方法,它还有三个属性,一个引用类型 String定义的 pname,一个基本类型 int定义的 page,还有一个引用类型 Address ,这是一个自定义类,这个类也包含两个属性 pprovices 和 city 。

接着看测试内容,首先我们创建一个Person 类的对象 p1,其pname 为zhangsan,page为21,地址类 Address 两个属性为 湖北省和武汉市。接着我们调用 clone() 方法复制另一个对象 p2,接着打印这两个对象的内容。

从第 1 行和第 3 行打印结果:

p1:com.ys.test.Person@349319f9

p2:com.ys.test.Person@258e4566

可以看出这是两个不同的对象。

从第 5 行和第 6 行打印的对象内容看,原对象 p1 和克隆出来的对象 p2 内容完全相同。

代码中我们只是更改了克隆对象 p2 的属性 Address 为湖北省荆州市(原对象 p1 是湖北省武汉市) ,但是从第 7 行和第 8 行打印结果来看,原对象 p1 和克隆对象 p2 的 Address 属性都被修改了。

也就是说对象 Person 的属性 Address,经过 clone 之后,其实只是复制了其引用,他们指向的还是同一块堆内存空间,当修改其中一个对象的属性 Address,另一个也会跟着变化。

6aa67bcdf042b0c83fdfc54ce44d9f3c.png

浅拷贝:创建一个新对象,然后将当前对象的非静态字段复制到该新对象,如果字段是值类型的,那么对该字段执行复制;如果该字段是引用类型的话,则复制引用但不复制引用的对象。因此,原始对象及其副本引用同一个对象。

深拷贝

弄清楚了浅拷贝,那么深拷贝就很容易理解了。

深拷贝:创建一个新对象,然后将当前对象的非静态字段复制到该新对象,无论该字段是值类型的还是引用类型,都复制独立的一份。当你修改其中一个对象的任何内容时,都不会影响另一个对象的内容。

c71664f8fdaccc7c9af715835e0e3c25.png

那么该如何实现深拷贝呢?Object 类提供的 clone 是只能实现 浅拷贝的。

如何实现深拷贝?

深拷贝的原理我们知道了,就是要让原始对象和克隆之后的对象所具有的引用类型属性不是指向同一块堆内存,这里有三种实现思路。

①、让每个引用类型属性内部都重写clone() 方法

既然引用类型不能实现深拷贝,那么我们将每个引用类型都拆分为基本类型,分别进行浅拷贝。比如上面的例子,Person 类有一个引用类型 Address(其实String 也是引用类型,但是String类型有点特殊,后面会详细讲解),我们在 Address 类内部也重写 clone 方法。如下:

Address.class:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

packagecom.ys.test;public class Address implementsCloneable{privateString provices;privateString city;public voidsetAddress(String provices,String city){this.provices =provices;this.city =city;

}

@OverridepublicString toString() {return "Address [provices=" + provices + ", city=" + city + "]";

}

@Overrideprotected Object clone() throwsCloneNotSupportedException {return super.clone();

}

}

View Code

Person.class 的 clone() 方法:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

@Overrideprotected Object clone() throwsCloneNotSupportedException {

Person p= (Person) super.clone();

p.address=(Address) address.clone();returnp;

}

View Code

测试还是和上面一样,我们会发现更改了p2对象的Address属性,p1 对象的 Address 属性并没有变化。

但是这种做法有个弊端,这里我们Person 类只有一个 Address 引用类型,而 Address 类没有,所以我们只用重写 Address 类的clone 方法,但是如果 Address 类也存在一个引用类型,那么我们也要重写其clone 方法,这样下去,有多少个引用类型,我们就要重写多少次,如果存在很多引用类型,那么代码量显然会很大,所以这种方法不太合适。

②、利用序列化

序列化是将对象写到流中便于传输,而反序列化则是把对象从流中读取出来。这里写到流中的对象则是原始对象的一个拷贝,因为原始对象还存在 JVM 中,所以我们可以利用对象的序列化产生克隆对象,然后通过反序列化获取这个对象。

注意每个需要序列化的类都要实现 Serializable 接口,如果有某个属性不需要序列化,可以将其声明为 transient,即将其排除在克隆属性之外。

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

//深度拷贝

public Object deepClone() throwsException{//序列化

ByteArrayOutputStream bos = newByteArrayOutputStream();

ObjectOutputStream oos= newObjectOutputStream(bos);

oos.writeObject(this);//反序列化

ByteArrayInputStream bis = newByteArrayInputStream(bos.toByteArray());

ObjectInputStream ois= newObjectInputStream(bis);returnois.readObject();

}

View Code

因为序列化产生的是两个完全独立的对象,所有无论嵌套多少个引用类型,序列化都是能实现深拷贝的。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值