平时我们从数据库查询出 po 对象,要返回给前端时,会有另一个对象 vo,此时我们需要将 po 的值复制给 vo,如果是你,你会怎么做呢?
有时我们除了复制之外,还要求 po 参数值的改变不能影响到 vo,也就是 po 和 vo 是两个独立的个体,此时我们又需要怎么做呢?
带着这些疑问,我们一起来看下今天所要讲解的关于对象复制的知识点。
一、什么是浅拷贝和深拷贝浅拷贝
- 对于基本数据类型的成员变量,浅拷贝直接进行值传递,也就是将属性值复制了一份给新的成员变量
- 对于引用数据类型的成员变量,比如成员变量是数组、某个类的对象等,浅拷贝就是引用的传递,也就是将成员变量的引用(内存地址)复制了一份给新的成员变量,他们指向的是同一个事例。在一个对象修改成员变量的值,会影响到另一个对象中成员变量的值。
深拷贝
- 对于基本数据类型,深拷贝复制所有基本数据类型的成员变量的值
- 对于引用数据类型的成员变量,深拷贝申请新的存储空间,并复制该引用对象所引用的对象,也就是将整个对象复制下来。所以在一个对象修改成员变量的值,不会影响到另一个对象成员变量的值。
- 实现 Cloneable
- 重写 clone()方法,并声明为 public
- 调用 super.clone()
copydemo
@Data
publicclassCopyDemoimplementsCloneable{
privateintage;
privateUseruser;
@Override
publicCopyDemoclone(){
try{
CopyDemoclone=(CopyDemo)super.clone();
returnclone;
}catch(CloneNotSupportedExceptione){
thrownewAssertionError();
}
}
}
user
@Data
publicclassUser{
privateStringname;
}
使用
@Service
publicclassCopyServiceDemo{
publicstaticvoidmain(String[]args){
CopyDemosource=newCopyDemo();
source.setAge(10);
Useruser=newUser();
user.setName("user-旧名字");
source.setUser(user);
CopyDemotarget=source.clone();
System.out.println("改变之前");
System.out.println("source:" source.getAge());
System.out.println("target:" target.getAge());
System.out.println("source-user:" source.getUser().getName());
System.out.println("target-user:" target.getUser().getName());
source.setAge(20);
user.setName("user-新名字");
System.out.println("改变之后");
System.out.println("source:" source.getAge());
System.out.println("target:" target.getAge());
System.out.println("source-user:" source.getUser().getName());
System.out.println("target-user:" target.getUser().getName());
}
}
结果
改变之前
source:10
target:10
source-user:user-旧名字
target-user:user-旧名字
改变之后
source:20
target:10
source-user:user-新名字
target-user:user-新名字
从以上结果可以看出
- 修改 source 的 age,并不会影响到拷贝之后的 target 的 age
- 修改 source 的 user 的 name,会影响到拷贝之后的 targe 的 user 的 name,因为 target 的 user 跟 source 的 user 所指向的是同一个 user 实例。
Apache BeanUtils 属于比较古老的工具类,由于存在性能问题,阿里巴巴手册明确禁止使用该工具类
性能差的原因是:力求做得完美, 在代码中增加了非常多的校验、兼容、日志打印等代码,过度的包装导致性能下降严重。
3、Spring BeanUtils
Spring BeanUtils 和上面所提到的 apche 得很像,但是在效率上比 apache 得更高
Spring BeanUtils 的 copyProperties() 方法,第一个是源对象,第二个是目标对象。和 Apache BeanUtils 正好相反,要注意避免踩坑。
importorg.springframework.beans.BeanUtils;
CopyDemotarget=newCopyDemo();
BeanUtils.copyProperties(source,target);
Spring 还为我们提供了一种基于 Cglib 的浅拷贝方式 BeanCopier,引入 spring-core 依赖包后即可使用,它被认为是取代 BeanUtils 的存在。
以下是自己封装的工具类:
importorg.springframework.cglib.beans.BeanCopier;
publicstatic<T>TcopyByClass(Objectsrc,Class<T>clazz){
BeanCopiercopier=BeanCopier.create(src.getClass(),clazz,false);
Tto=newInstance(clazz);
copier.copy(src,to,null);
returnto;
}
publicstatic<T>TnewInstance(Class<?>clazz){
try{
return(T)clazz.newInstance();
}catch(InstantiationExceptione){
thrownewRuntimeException(e);
}catch(IllegalAccessExceptione){
thrownewRuntimeException(e);
}
}
publicstaticvoidcopyByObj(Objectsrc,Objectdist){
BeanCopiercopier=BeanCopier
.create(src.getClass(),dist.getClass(),false);
copier.copy(src,dist,null);
}
使用
CopyDemotarget=copyByClass(source,CopyDemo.class);
手动 new 新的对象,一个属性一个属性的 set 过去,属性多的话,这样非常麻烦
publicstaticvoidmain(String[]args){
CopyDemosource=newCopyDemo();
source.setAge(10);
Useruser=newUser();
user.setName("user-旧名字");
source.setUser(user);
CopyDemotarget=newCopyDemo();
target.setAge(source.getAge());
UsertargetUser=newUser();
targetUser.setName(source.getUser().getName());
target.setUser(targetUser);
System.out.println("改变之前");
System.out.println("source:" source.getAge());
System.out.println("target:" target.getAge());
System.out.println("source-user:" source.getUser().getName());
System.out.println("target-user:" target.getUser().getName());
source.setAge(20);
user.setName("user-新名字");
System.out.println("改变之后");
System.out.println("source:" source.getAge());
System.out.println("target:" target.getAge());
System.out.println("source-user:" source.getUser().getName());
System.out.println("target-user:" target.getUser().getName());
}
改变之前
source:10
target:10
source-user:user-旧名字
target-user:user-旧名字
改变之后
source:20
target:10
source-user:user-新名字
target-user:user-旧名字
Processfinishedwithexitcode0
- 拷贝的对象中还包含其他对象的话,包含的对象也需要重写 clone 方法
- super.clone()其实是浅拷贝,所以在重写 CopyDemo 类的 clone()方法时,user 对象需要调用 user.clone()重新赋值
CopyDemo
@Data
publicclassCopyDemoimplementsCloneable{
privateintage;
privateUseruser;
@Override
publicCopyDemoclone(){
try{
CopyDemocopyDemo=(CopyDemo)super.clone();
copyDemo.setUser(this.user.clone());
returncopyDemo;
}catch(CloneNotSupportedExceptione){
thrownewAssertionError();
}
}
}
User
@Data
publicclassUserimplementsCloneable{
privateStringname;
@Override
publicUserclone(){
try{
Userclone=(User)super.clone();
returnclone;
}catch(CloneNotSupportedExceptione){
thrownewAssertionError();
}
}
}
使用
CopyDemotarget=source.clone();
Java 提供了序列化的能力,我们可以先将源对象进行序列化,再反序列化生成拷贝对象。但是,使用序列化的前提是拷贝的类(包括其成员变量)需要实现 Serializable 接口。Apache Commons Lang 包对 Java 序列化进行了封装:SerializationUtils,我们可以直接使用它。
@Data
publicclassCopyDemoimplementsSerializable{
privatestaticfinallongserialVersionUID=-9820808986091860L;
privateintage;
privateUseruser;
}
@Data
publicclassUserimplementsSerializable{
privatestaticfinallongserialVersionUID=1900781036567192607L;
privateStringname;
}
使用
importorg.apache.commons.lang3.SerializationUtils;
CopyDemotarget=SerializationUtils.clone(source);
利用 json 将对象转为 json,再将 json 转为对象,本质上是反射
//对象
StringjsonString=JSON.toJSONString(source);
CopyDemotarget=JSON.parseObject(jsonString,CopyDemo.class);
//集合
List<CopyDemo>sourceList=Lists.newArrayList();
StringjsonString=JSON.toJSONString(sourceList);
List<CopyDemo>targetList=JSON.parseArray(json,CopyDemo.class);
orika 是深拷贝,但是遇到多层签到数组,clone 会有问题,谨慎使用
四、总结如果对象中只有基本数据类型或者引用数据类型不会改动,则可以使用浅拷贝
如果存在引用数据类型且会改动,则可以使用深拷贝
具体使用拷贝中的哪个方法,需要具体情况具体分析,比如性能考虑、便捷考虑、依赖引入的考虑等等。
今天只是列出了一些常用的方法,还有其他的拷贝方法,可以自行搜索,多学习,多实践。
我是臻大虾,你的支持是对我不断创作的极大鼓励,咱们下期见。
关注公众号:臻大虾 分享java后端技术干货,每天进步一点点
,