点关注,不迷路;持续更新Java相关技术及资讯!!!
前言
在阿里Java开发手册中,有这么一条建议:慎用 Object 的 clone 方法来拷贝对象。对象 clone 方法默认是浅拷贝,若想实现深拷贝需覆写 clone 方法实现域对象的深度遍历式拷贝 。Java中的对象拷贝,有浅拷贝和深拷贝两种,如果没有搞清楚这两者的区别,那么可能会给自己的代码埋下隐患。
什么是浅拷贝和深拷贝
浅拷贝:被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。换言之,浅复制仅仅复制所考虑的对象,而不复制它所引用的对象。
深拷贝:被复制对象的所有变量都含有与原来的对象相同的值,除去那些引用其他对象的变量。那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象。换言之,深复制把要复制的对象所引用的对象都复制了一遍。
通过上面的结论,我们可以看出浅拷贝和深拷贝的区别就在于所要拷贝的对象的引用数据类型,如果是拷贝一份引用,那么这是浅拷贝,如果是新建一个对象,那么这就是深拷贝。
clone方法
在Java的Object对象中,有clone这个方法。它被声明为了protected,所以我们可以在其子类中使用它。这里需要注意的是,我们在子类中使用clone方法时,子类需要实现Cloneable接口,否则会抛出java.lang.CloneNotSupportedException异常。
有如下对象
如下实体类都使用了Lombok。
Address.java
@DatapublicclassAddress{privateString address;}
Person.java
@DatapublicclassPersonimplementsCloneable{privateString name;privateInteger age;privateAddress address;@OverrideprotectedObjectclone()throwsCloneNotSupportedException{returnsuper.clone(); }}
浅拷贝
浅拷贝,示例代码如下:
publicstaticvoidmain(String[] args)throwsCloneNotSupportedException{ Person person =newPerson(); person.setName("Happyjava"); person.setAge(33); Address address =newAddress(); address.setAddress("浙江杭州"); person.setAddress(address); Person newPerson = (Person) person.clone(); System.out.println(person == newPerson); System.out.println(person.getAddress() == newPerson.getAddress());}
通过 == 比较是否是同一个对象。其运行结果如下:
falsetrue说明了通过clone方法拷贝出来的对象,与原对象并不是同一个对象。而person.getAddress() == newPerson.getAddress() 的比较是true,说明了二者的引用都是指向同一个对象。这就是浅拷贝,引用类型还是指向原来的对象。
浅拷贝存在的问题
很多时候,我们拷贝一个对象,是希望完全进行深度拷贝的。浅拷贝存在的问题就是,对于原对象引用类型的属性进行修改,拷贝出来的对象也会受到影响(因为二者的引用都指向同一个对象)。如下代码:
publicstaticvoidmain(String[] args)throwsCloneNotSupportedException{ Person person =newPerson(); person.setName("Happyjava"); person.setAge(33); Address address =newAddress(); address.setAddress("浙江杭州"); person.setAddress(address); Person newPerson = (Person) person.clone(); newPerson.getAddress().setAddress("广东省深圳市"); System.out.println(person.getAddress().getAddress());}
运行结果如下:
通过newPerson把address设置为“广东省深圳市”,person的address也变成了"广东省深圳市"。
这种情况,如果我们没有注意,是很容易造成生产事故的。
深拷贝
通过clone方法实现深拷贝,是一件比较麻烦的事情,因为我们需要手动在clone方法里拷贝引用类型。代码修改如下:
Address.java
@DatapublicclassAddressimplementsCloneable{privateString address;@OverrideprotectedObjectclone()throwsCloneNotSupportedException{returnsuper.clone(); }}
Person.java
@DatapublicclassPersonimplementsCloneable{privateString name;privateInteger age;privateAddress address;@OverrideprotectedObjectclone()throwsCloneNotSupportedException{ Person newPerson = (Person)super.clone(); newPerson.address = (Address)this.address.clone();returnnewPerson; }}
通过clone方法实现深拷贝,我们需要在Person的clone方法里调用address的clone方法,并且手动设置clone出来的新的address。
再次执行上面的测试代码,运行结果如下:
通过序列化实现深拷贝
通过clone方法实现深拷贝是比较麻烦的一件事情,这里推荐大家可以通过序列化、反序列化的方式实现深拷贝。我们可以直接使用commons-lang3包的序列化、反序列工具类。
引入依赖
org.apache.commonscommons-lang33.8.1
序列化需要实现Serializable接口,Person和Address类都需要实现。测试代码如下:
publicstaticvoidmain(String[] args)throwsCloneNotSupportedException{ Person person =newPerson(); person.setName("Happyjava"); person.setAge(33); Address address =newAddress(); address.setAddress("浙江杭州"); person.setAddress(address);// 序列化byte[] serialize = SerializationUtils.serialize(person);// 反序列化Person newPerson = SerializationUtils.deserialize(serialize); System.out.println(person == newPerson); System.out.println(person.getAddress() == newPerson.getAddress());}
运行结果如下:
总结
拷贝对象,如果直接通过clone方法进行拷贝,是很容易出现问题的。我们要清楚的知道浅拷贝和深拷贝的区别。
本文到这里就结束了,喜欢的朋友可以帮忙转发和关注一下,感谢支持!
为了感谢支持我的朋友!整理了一份Java高级架构资料、Spring源码分析、Dubbo、Redis、Netty、zookeeper、Spring cloud、分布式等资料
关注公众号:Java耕耘者(免费获取)Java源码分析技术群:818491202 进群验证:33
这份知识尤其适合:
近期想跳槽,要面试的Java程序员,查漏补缺,以便尽快弥补短板;
转载自;Happyjava
网友评论