当前位置:首页 > 经验 >

深拷贝和浅拷贝的简单理解(深拷贝解决浅拷贝的问题)

来源:原点资讯(www.yd166.com)时间:2022-10-24 22:07:09作者:YD166手机阅读>>

前言

平时我们从数据库查询出 po 对象,要返回给前端时,会有另一个对象 vo,此时我们需要将 po 的值复制给 vo,如果是你,你会怎么做呢?

有时我们除了复制之外,还要求 po 参数值的改变不能影响到 vo,也就是 po 和 vo 是两个独立的个体,此时我们又需要怎么做呢?

带着这些疑问,我们一起来看下今天所要讲解的关于对象复制的知识点。

一、什么是浅拷贝和深拷贝

浅拷贝

  • 对于基本数据类型的成员变量,浅拷贝直接进行值传递,也就是将属性值复制了一份给新的成员变量
  • 对于引用数据类型的成员变量,比如成员变量是数组、某个类的对象等,浅拷贝就是引用的传递,也就是将成员变量的引用(内存地址)复制了一份给新的成员变量,他们指向的是同一个事例。在一个对象修改成员变量的值,会影响到另一个对象中成员变量的值。

深拷贝

  • 对于基本数据类型,深拷贝复制所有基本数据类型的成员变量的值
  • 对于引用数据类型的成员变量,深拷贝申请新的存储空间,并复制该引用对象所引用的对象,也就是将整个对象复制下来。所以在一个对象修改成员变量的值,不会影响到另一个对象成员变量的值。
二、浅拷贝1、clone
  • 实现 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 实例。
2、Apache BeanUtils(不推荐)

Apache BeanUtils 属于比较古老的工具类,由于存在性能问题,阿里巴巴手册明确禁止使用该工具类

性能差的原因是:力求做得完美, 在代码中增加了非常多的校验、兼容、日志打印等代码,过度的包装导致性能下降严重。

深拷贝和浅拷贝的简单理解,深拷贝解决浅拷贝的问题(1)

3、Spring BeanUtils

Spring BeanUtils 和上面所提到的 apche 得很像,但是在效率上比 apache 得更高

Spring BeanUtils 的 copyProperties() 方法,第一个是源对象,第二个是目标对象。和 Apache BeanUtils 正好相反,要注意避免踩坑。

importorg.springframework.beans.BeanUtils; CopyDemotarget=newCopyDemo(); BeanUtils.copyProperties(source,target); 4、Spring BeanCopier

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); 三、深拷贝1、构造方法-new

手动 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 2、重载 clone()方法

  • 拷贝的对象中还包含其他对象的话,包含的对象也需要重写 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(); 3、Apache Commons Lang 序列化方式

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); 4、json 转化方式

利用 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); 5、Orika

orika 是深拷贝,但是遇到多层签到数组,clone 会有问题,谨慎使用

四、总结

如果对象中只有基本数据类型或者引用数据类型不会改动,则可以使用浅拷贝

如果存在引用数据类型且会改动,则可以使用深拷贝

具体使用拷贝中的哪个方法,需要具体情况具体分析,比如性能考虑、便捷考虑、依赖引入的考虑等等。

今天只是列出了一些常用的方法,还有其他的拷贝方法,可以自行搜索,多学习,多实践。


我是臻大虾,你的支持是对我不断创作的极大鼓励,咱们下期见。

关注公众号:臻大虾 分享java后端技术干货,每天进步一点点

深拷贝和浅拷贝的简单理解,深拷贝解决浅拷贝的问题(2)

深拷贝和浅拷贝的简单理解,深拷贝解决浅拷贝的问题(3)

栏目热文

深拷贝解决浅拷贝的问题(深拷贝的缺点)

深拷贝解决浅拷贝的问题(深拷贝的缺点)

作者:小丑转发链接:https://mp.weixin.qq.com/s/i_vGi8C5PBa_KOV7MtVPWQ前...

2022-10-24 22:51:29查看全文 >>

浅拷贝和深拷贝的值会不会变(高拷贝和低拷贝的区别)

浅拷贝和深拷贝的值会不会变(高拷贝和低拷贝的区别)

前面我们曾经聊到列表从内存的角度是什么样的?有兴趣的可以关注我,看一下《python之从内存读写的角度,学习并玩转lis...

2022-10-24 22:44:50查看全文 >>

为什么要深拷贝浅拷贝(零拷贝和深拷贝)

为什么要深拷贝浅拷贝(零拷贝和深拷贝)

关于对象的拷贝,大部分时间我们用的都是浅拷贝,比如赋值符号(“=”)以及memcpy()等。那么既然浅拷贝这么简单,为...

2022-10-24 22:08:28查看全文 >>

深拷贝和浅拷贝和零拷贝的区别(深拷贝与浅拷贝示意图)

深拷贝和浅拷贝和零拷贝的区别(深拷贝与浅拷贝示意图)

原型模式也是创建对象的一种方式,它一般用在这样的场景:系统中存在大量相同或相似对象的创建问题,如果用传统的构造函数来创建...

2022-10-24 22:15:15查看全文 >>

深拷贝和浅拷贝的方法(深拷贝的方法及优缺点)

深拷贝和浅拷贝的方法(深拷贝的方法及优缺点)

由于JavaScript中对象是引用类型,保存的是地址,深、浅拷贝的区别是,当拷贝结束后,在一定程度上改变原对象中的某一...

2022-10-24 22:44:42查看全文 >>

简述浅拷贝和深拷贝的区别(浅拷贝深拷贝哪个是原地操作)

简述浅拷贝和深拷贝的区别(浅拷贝深拷贝哪个是原地操作)

使用场景:开发的时候对引用类型进行处理,为了避免修改一个引用类型数据时,其他用这个变量来赋值的变量也都被改变了。如下例子...

2022-10-24 22:53:14查看全文 >>

深拷贝和浅拷贝图解(深拷贝和浅拷贝的方法)

深拷贝和浅拷贝图解(深拷贝和浅拷贝的方法)

作者:南枝向暖北枝寒MA 原文链接:https://blog.csdn.net/mall_lucy/article/de...

2022-10-24 22:21:57查看全文 >>

谈谈你对浅拷贝和深拷贝的理解(零拷贝和深拷贝)

谈谈你对浅拷贝和深拷贝的理解(零拷贝和深拷贝)

先说下自己的理解吧,浅拷贝,即在定义一个类A,使用类似A obj; A obj1(obj);或者A obj1 = obj...

2022-10-24 22:24:54查看全文 >>

深拷贝和浅拷贝有哪些应用

深拷贝和浅拷贝有哪些应用

大家好我是发哥,本期说说C 深拷贝和浅拷贝。对于基本类型的数据以及简单的对象,它们之间的拷贝非常简单,就是按位复制内存...

2022-10-24 22:41:01查看全文 >>

高拷贝和低拷贝的区别(深拷贝浅拷贝零拷贝的区别是什么)

高拷贝和低拷贝的区别(深拷贝浅拷贝零拷贝的区别是什么)

一、如何判断我的质粒是高拷贝还是低拷贝质粒?高拷贝质粒每 1 ml LB 培养物应产生 3-5 ug DNA,而低拷贝质...

2022-10-24 22:41:09查看全文 >>

文档排行