0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

为什么有时候会写出烂代码

深圳东裕光大 来源:Hollis 作者:Hollis 2021-08-27 10:23 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

本文的内容是最近我刚刚遇到的一个问题,问题代码是我自己写的,也是我自己写单元测试的时候发现的,也是我自己修复的,修复完之后,我反思了一下:这样的问题代码,我实习的时候都写不出来。

可是为什么我就写出来了呢?其实还是因为有些知识没那么扎实了~就容易被忽略了,于是我在团队群里面强调了一下这个问题:

所以,本文主要是关于BeanUtils工具的属性拷贝以及深拷贝、浅拷贝等问题的。好了开始正文,介绍下问题代码是什么,为什么有问题,又符合修改?

在日常开发中,我们经常需要给对象进行赋值,通常会调用其set/get方法,有些时候,如果我们要转换的两个对象之间属性大致相同,会考虑使用属性拷贝工具进行。

如我们经常在代码中会对一个数据结构封装成DO、SDO、DTO、VO等,而这些Bean中的大部分属性都是一样的,所以使用属性拷贝类工具可以帮助我们节省大量的set和get操作。

市面上有很多类似的工具类,比较常用的有

1、Spring BeanUtils

2、Cglib BeanCopier

3、Apache BeanUtils

4、Apache PropertyUtils

5、Dozer

6、MapStucts

这里面我比较建议大家使用的是MapStructs。

最近我们有个新项目,要创建一个新的应用,因为我自己分析过这些工具的效率,也去看过他们的实现原理,比较下来之后,我觉得MapStruct是最适合我们的,于是就在代码中引入了这个框架。

另外,因为Spring的BeanUtils用起来也比较方便,所以,代码中对于需要beanCopy的地方主要在使用这两个框架。

我们一般是这样的,如果是DO和DTO/Entity之间的转换,我们统一使用MapStruct,因为他可以指定单独的Mapper,可以自定义一些策略。

如果是同对象之间的拷贝(如用一个DO创建一个新的DO),或者完全不相关的两个对象转换,则使用Spring的BeanUtils。

刚开始都没什么问题,但是后面我在写单测的时候,发现了一个问题。

问题

先来看看我们是在什么地方用的Spring的BeanUtils

我们的业务逻辑中,需要对订单信息进行修改,在更改时,不仅要更新订单的上面的属性信息,还需要创建一条变更流水。

而变更流水中同时记录了变更前和变更后的数据,所以就有了以下代码:

//从数据库中查询出当前订单,并加锁 OrderDetail orderDetail = orderDetailDao.queryForLock(); //copy一个新的订单模型 OrderDetail newOrderDetail = new OrderDetail();

BeanUtils.copyProperties(orderDetail, newOrderDetail);//对新的订单模型进行修改逻辑操作

newOrderDetail.update(); //使用修改前的订单模型和修改后的订单模型组装出订单变更流水

OrderDetailStream orderDetailStream = new OrderDetailStream(); orderDetailStream.create(orderDetail, newOrderDetail);

大致逻辑是这样的,因为创建订单变更流水的时候,需要一个改变前的订单和改变后的订单。所以我们想到了要new一个新的订单模型,然后操作新的订单模型,避免对旧的有影响。

但是,就是这个BeanUtils.copyProperties的过程其实是有问题的。

因为BeanUtils在进行属性copy的时候,本质上是浅拷贝,而不是深拷贝。

浅拷贝?深拷贝?

什么是浅拷贝和深拷贝?来看下概念。

1、浅拷贝:对基本数据类型进行值传递,对引用数据类型进行引用传递般的拷贝,此为浅拷贝。

2、深拷贝:对基本数据类型进行值传递,对引用数据类型,创建一个新的对象,并复制其内容,此为深拷贝。

我们举个实际例子,来看下为啥我说BeanUtils.copyProperties的过程是浅拷贝。

先来定义两个类:

public class Address { private String province; private String city; private String area; //省略构造函数和setter/getter } class User { private String name; private String password; private Address address; //省略构造函数和setter/getter }

然后写一段测试代码:

User user = new User(“Hollis”, “hollischuang”); user.setAddress(new Address(“zhejiang”, “hangzhou”, “binjiang”)); User newUser = new User(); BeanUtils.copyProperties(user, newUser); System.out.println(user.getAddress() == newUser.getAddress());

以上代码输出结果为:true

即,我们BeanUtils.copyProperties拷贝出来的newUser中的address对象和原来的user中的address对象是同一个对象。

可以尝试着修改下newUser中的address对象:

newUser.getAddress().setCity(“shanghai”); System.out.println(JSON.toJSONString(user)); System.out.println(JSON.toJSONString(newUser));

输出结果:

{“address”:{“area”:“binjiang”,“city”:“shanghai”,“province”:“zhejiang”},“name”:“Hollis”,“password”:“hollischuang”} {“address”:{“area”:“binjiang”,“city”:“shanghai”,“province”:“zhejiang”},“name”:“Hollis”,“password”:“hollischuang”}

可以发现,原来的对象也受到了修改的影响。

这就是所谓的浅拷贝!

74732e18-f46d-11eb-9bcf-12bb97331649.png

如何进行深拷贝

发现问题之后,我们就要想办法解决,那么如何实现深拷贝呢?

1、实现Cloneable接口,重写clone()

在Object类中定义了一个clone方法,这个方法其实在不重写的情况下,其实也是浅拷贝的。

如果想要实现深拷贝,就需要重写clone方法,而想要重写clone方法,就必须实现Cloneable,否则会报CloneNotSupportedException异常。

将上述代码修改下,重写clone方法:

public class Address implements Cloneable{ private String province; private String city; private String area; //省略构造函数和setter/getter@Override public Object clone() throws CloneNotSupportedException { return super.clone();

} } class User implements Cloneable{ private String name; private String password; private Address address; //省略构造函数和setter/getter @Override protected Object clone() throws CloneNotSupportedException { User user = (User)super.clone();

user.setAddress((Address)address.clone()); return user; } }

之后,在执行一下上面的测试代码,就可以发现,这时候newUser中的address对象就是一个新的对象了。

这种方式就能实现深拷贝,但是问题是如果我们在User中有很多个对象,那么clone方法就写的很长,而且如果后面有修改,在User中新增属性,这个地方也要改。

那么,有没有什么办法可以不需要修改,一劳永逸呢?

2、序列化实现深拷贝

我们可以借助序列化来实现深拷贝。先把对象序列化成流,再从流中反序列化成对象,这样就一定是新的对象了。

序列化的方式有很多,比如我们可以使用各种JSON工具,把对象序列化成JSON字符串,然后再从字符串中反序列化成对象。

如使用fastjson实现:

User newUser = JSON.parseObject(JSON.toJSONString(user), User.class);

也可实现深拷贝。

除此之外,还可以使用Apache Commons Lang中提供的SerializationUtils工具实现。

我们需要修改下上面的User和Address类,使他们实现Serializable接口,否则是无法进行序列化的。

class User implements Serializable class Address implements Serializable

然后在需要拷贝的时候:

User newUser = (User) SerializationUtils.clone(user);

同样,也可以实现深拷贝啦~!

总结

当我们使用各类BeanUtils的时候,一定要注意是浅拷贝还是深拷贝,浅拷贝的结果就是两个对象中的引用对象都是同一个地址,只要发生改变,都会有影响。

想要实现深拷贝,有很多种办法,其中比较常用的就是实现Cloneable接口重写clone方法,还有使用序列化+反序列化创建新对象。

好了,以上就是今天的全部内容了。

责任编辑:haq

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 代码
    +关注

    关注

    30

    文章

    4941

    浏览量

    73151
  • 工具
    +关注

    关注

    4

    文章

    317

    浏览量

    28739

原文标题:这样的烂代码,我实习的时候都写不出来!

文章出处:【微信号:sztonyu,微信公众号:深圳东裕光大】欢迎添加关注!文章转载请注明出处。

收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    搞笑诺贝尔:科学界的邪修盛宴

    真正的智慧有时候就藏在不为人关注的日常里
    的头像 发表于 11-21 22:17 3129次阅读
    搞笑诺贝尔:科学界的邪修盛宴

    伺服电机和步进电机的核心区别

    大家在运动控制的时候是用伺服电机的还是用步进电机做定位控制了?为什么有时候用伺服电机,有时候又步进电机?这是什么原因呢?本期我们就一起来探讨一下步进电机和伺服电机的区别!
    的头像 发表于 10-15 14:32 1373次阅读
    伺服电机和步进电机的核心区别

    H743的LPUART1接收数据有时候出错怎么解决?

    MODBUS协议,从机回复的都是正确的。LPUART1接收到就有时不正确。 波特率9600,时钟用的是内部CSI。 同一块儿板子有时出现,有时正常。有的板子出现,有的板子不出现。
    发表于 09-22 06:58

    使用ADCPRO软件进行测试的时候,点击左上角的“Acquire”按键后,有时数据采不上来,是什么原因?

    在使用ADCPRO软件进行测试的时候,点击左上角的“Acquire”按键后,有时数据采不上来,左上角的状态栏一直闪烁,下面采集状态显示100%。有时候是好的,可以采集上来。请问是什么原因。。。
    发表于 02-14 06:31

    用ADS1256测微安级电流,采样电阻用的3.3欧姆,有时候会出现得到的AD值不准,为什么?

    用ADS1256测微安级电流,采样电阻用的3.3欧姆,有时候会出现得到的AD值不准,应该是干扰的原因,我自己调试的时候没出现这个问题,在车间里出现了这个问题,用手摸摸采样电路,有时候会恢复正常。求解??? 谢谢!
    发表于 02-12 06:28

    用ADS1298在16K采样率下,SPI SCLK为18MHZ.每次中断,读走8个通道数据,有时候后四个通道读出均为0,为什么?

    我用ADS1298在16K采样率下,SPI SCLK为18MHZ.每次中断,读走8个通道数据,但是有时候后四个通道读出均为0,重新复位就好了。但是通过示波器看却是有SCLK发出,读数据很不稳定。
    发表于 02-11 08:31

    把两个DAC121S101连接成菊花链结构,为什么第二片的输出有时候不是想要的电压?

    我把两个DAC121S101连接成菊花链结构,为什么第二片的输出有时候不是我想要的电压?
    发表于 01-23 07:32

    为什么ad在输入一个直流的时候有时输出电压不对?

    正常时候是1.65v,但有时输出的24位高低电平会是0.2v,用示波器看的。
    发表于 01-15 06:27

    多通道的adc在切换通道的时候需要注意些什么?

    我现在切换通道的时候,数据更新出现了点问题,有时候明明切换到通道2了,但是读的数据却是通道1的,但是有时候又是正常的! 用的adc芯片是iic通讯接口的,18位的adc,我现在采样速率是15SPS。
    发表于 01-09 06:07

    TCA8418用来做键盘扩展,有时候设备开关机或进入睡眠状态,启动或唤醒会出现键盘失灵现象,怎么解决?

    在TCA8418旁边有13.56MHZ的读卡模块,TCA8418用来做键盘扩展,有时候设备开关机,或进入睡眠状态,启动或唤醒会出现键盘失灵现象。8418是什么原因受干扰,有什么办法避免么,有什么好的解决方法。
    发表于 01-03 08:04

    ADS1191采集内部测试信TEST1Hz方波,有时候hen正常有时候不正常,是哪里出了问题?

    我采集内部测试信TEST1Hz方波,有时候hen正常 有时候不正常,不知道是哪里出现问题 很明显第一幅图是比较正常的 第二幅会出现问题。有没有大神知道这个问题是什么原因 这是我
    发表于 12-30 06:30

    在上电后,有时候会出现DAC1220E输出不清零的情况,怎么解决?

    ,DAC1220E电路图均按照规格书基本电路设计,周围电容均为NPO材质的,在使用过程中出现以下问题: 一、在上电后,有时候会出现DAC1220E输出不清零,为2.5V输出; 二、一旦出现上电不清
    发表于 12-18 07:24

    DS90UB954A RIN1有时无法Lock 913输入的FPD-link信号,为什么?

    DS90UB954A RIN1 有时候无法Lock 913输入的FPD-link信号。但是从物理上可以在954输入端可以量到信号。MIPI端无输出信号。 这种状态下0x4D 低两位一直为0x40。
    发表于 12-17 08:36

    ADS1256第一次上电的时候,采集的ADC信号是实际值的一半,为什么?

    最近在用ADS1256这一款ADC芯片,出现了一个很怪异的问题。当我第一次上电的时候,采集的ADC信号是实际值的一半,重新上电后又正常了。 但是并不是每次第一次上电ADC读取错误。 这种现象有时候
    发表于 12-13 15:33

    ADS1282读写寄存器读不出正确的结果,有时候是全0,为什么?

    的什么值,再回读寄存器内容的时候就读不出正确的结果,有时候是全0,有时候是前几个寄存器值对而后边的不对,还有完全不对的情况。读采集数据也是无规律的乱变的数值或者全0或者满量程值等错误数据,与实际输入
    发表于 12-13 06:15