浅谈BeanUtils的拷贝,深度克隆

news/2024/7/5 0:55:39 标签: java, python

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

1、BeanUtil本地简单测试
在项目中由于需要对某些对象进行深度拷贝然后进行持久化操作,想到了apache和spring都提供了BeanUtils的深度拷贝工具包,自己写了几个Demo做测试,定义了两个类User和Person,其中User的属性引用了Person类。

复制代码

public class User {
    private int id;
    private String username;// 用户姓名
    private String sex;// 性别
    private Date birthday;// 生日
    private String address;// 地址

    private Person person; //包装类

    //get set方法此处省略
}

复制代码

复制代码

//Person类
public class Person {
    private int id;
    private String userName ;
    private int age ;
    private String mobilePhone ;
    public  Person(){}
    public Person(int id,String userName, int age, String mobilePhone) {
        this.id = id;
        this.userName = userName;
        this.age = age;
        this.mobilePhone = mobilePhone;
    }

    //get set方法此处省略
}

复制代码

编写测试方法进行调研,主要是查看对象中包装的对象是否引用了同一个地址,从而判断是否是深度拷贝还是浅拷贝

复制代码

@Test
    public void CopyTest(){
        User user=new User();
        user.setId(1);
        user.setSex("man");
        user.setUsername("Tison");
        user.setAddress("address");
        user.setBirthday(new Date());
        Person p=new Person();
        p.setUserName("p1");
        user.setPerson(p);
        User target=new User();
        BeanUtils.copyProperties(user,target);
        System.out.println(target.getAddress()==user.getAddress());
        System.out.println(target.getPerson()==user.getPerson());
        System.out.println(user.toString());
        System.out.println(target.toString());
    }

复制代码

打印结果:

1

2

3

4

false  (String属性的内存地址不相等)

false  (包装对象的内存地址不相等)

src.main.mybatis.User@7907ec20

src.main.mybatis.User@546a03af

两个对象的哈希码不相等,引用对象的地址也不相同,并且对包装对象的操作都是互不影响,简单测试下可以看到BeanUtils实现了深度拷贝的效果。

2、项目测试
但是到了本人所做的项目中,BeanUtils的效果就不是深度拷贝了,用伪代码进行简单说明:

复制代码

//source为A对象,target为B对象
BeanUtils.copyProperties(source,target);
//调用setSecret方法将B对象的某型包装属性set为null
setSecret(target);
//分别打印对比的结果
System.out.println(target.getUserInfo()==source.getUserInfo());
System.out.println(source.getUserInfo().hashCode());
System.out.println(target.getUserInfo().hashCode());

复制代码

1

2

3

4

//打印测试结果

true

1589531316

1589531316

两份对象里的包装对象内存地址比较结果为true,而且对象的哈希吗指向了同一位置。
显而易见,BeanUtils并未进行深度拷贝。本人在项目中正因为采用了BeanUtils的拷贝方法,在对两份对象的不同操作时都会互相影响导致持久化的异常,可见基于BeanUtils的拷贝方法并不是万能的,而且由于源码中采用反射机制,其性能也被许多博主诟病,在网上进行了综合调研,发现BeanUtils的copyProperties()方法的确存在着浅拷贝的情况,这对于持久化操作实体类的时候是很大的一个坑,那么最靠谱的深拷贝方法还是要序列化后写流的方法,只是该方法需要实现Serializable接口。

3、深拷贝
深复制(深克隆)被复制对象的所有变量都含有与原来的对象相同的值,除去那些引用其他对象的变量,那些引用其他对象的变量将指向被复制过的新对象,而不再试原有的那些被引用的对象,换言之,深复制把要复制的对象所引用的对象都复制了一遍。
把对象写到流里的过程是串行化(Serilization)过程,但是在Java程序师圈子里又非常形象地称为“冷冻”或者“腌咸菜(picking)”过程;而把对象从流中读出来的并行化(Deserialization)过程则叫做“解冻”或者“回鲜(depicking)”过程。应当指出的是,写在流里的是对象的一个拷贝,而原对象仍然存在于JVM里面,因此“腌成咸菜”的只是对象的一个拷贝,Java咸菜还可以回鲜。在Java语言里深复制一个对象,常常可以先使对象实现Serializable接口,然后把对象(实际上只是对象的一个拷贝)写到一个流里(腌成咸菜),再从流里读出来(把咸菜回鲜),便可以重建对象。

在项目中我们需要克隆的对象可能包含多层引用类型,这就要涉及到多层克隆问题,多层克隆不仅要将克隆对象实现序列化接口,引用对象也同样的要实现序列化接口:

复制代码

public class User implements Serializable{
    private int id;
    private String username;// 用户姓名
    private String sex;// 性别
    private Date birthday;// 生日
    private String address;// 地址
    private Person person; //引用类型

    public User myColon(){
        User copy=null;
        try {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(baos);
            oos.writeObject(this);
            //将流序列化成对象
            ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bais);
            copy = (User) ois.readObject();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
            return copy;
    }

    //此处省略get-set方法代码
}

复制代码

引用类型也需要实现Serializable接口,否则会序列化失败。

复制代码

public class Person implements Serializable {
    private int id;
    private String userName ;
    private int age ;
    private String mobilePhone ;
    public  Person(){}
    public Person(int id,String userName, int age, String mobilePhone) {
        this.id = id;
        this.userName = userName;
        this.age = age;
        this.mobilePhone = mobilePhone;
    }
    //此处省略get-set方法
}

复制代码

结论:

1

2

3

结论:

1、BeanUtils的copyProperties()方法并不是完全的深度克隆,在包含有引用类型的对象拷贝上就可能会出现引用对象指向同一个的情况,且该方法的性能低下,项目中一定要谨慎使用。

2、要实现高性能且安全的深度克隆方法还是实现Serializable接口,多层克隆时,引用类型均要实现Serializable接口。

 

 

 

IV. 小结
1. 深拷贝和浅拷贝
深拷贝

相当于创建了一个新的对象,只是这个对象的所有内容,都和被拷贝的对象一模一样而已,即两者的修改是隔离的,相互之间没有影响 
- 完全独立

浅拷贝

也是创建了一个对象,但是这个对象的某些内容(比如A)依然是被拷贝对象的,即通过这两个对象中任意一个修改A,两个对象的A都会受到影响

等同与新创建一个对象,然后使用=,将原对象的属性赋值给新对象的属性
需要实现Cloneable接口
2. 对象拷贝的两种方法
通过反射方式实现对象拷贝

主要原理就是通过反射获取所有的属性,然后反射更改属性的内容

通过代理实现对象拷贝

将原SourceA拷贝到目标DestB

创建一个代理 copyProxy 
在代理中,依次调用 SourceA的get方法获取属性值,然后调用DestB的set方法进行赋值

转载于:https://my.oschina.net/xiaominmin/blog/2964005


http://www.niftyadmin.cn/n/834749.html

相关文章

python中的字典生成式

#需求1:假设有20个学生,学生的分数在60~100之间,筛选出成绩在90分以上的学生 import random stuInfo {}for i in range(20):name westos str(i)score random.randint(60, 100)stuInfo[name] scoreprint(stuInfo) highscore {}for name,…

DOM 获取、DOM动态创建节点

一、Dom获取 1、全称:Document Object Model  文档对象模型 2、我们常用的节点类型 元素(标签)节点、文本节点、属性节点(也就是标签里的属性)、 3、document有个属性叫nodeType返回的是数字 1:代…

超8千Star,火遍Github的Python反直觉案例集!

大数据文摘授权转载 作者:Satwik Kansal 译者:暮晨 Python,是一个设计优美的解释型高级语言,它提供了很多能让程序员感到舒适的功能特性。 但有的时候,Python的一些输出结果对于初学者来说似乎并不是那么一目了然。 这…

Weex Eros快速入门

概述 随着Weex跨平台技术的持续火热,一时间涌现出了一大批基于Weex的开源解决方案,Weex Eros就是这么一个面向前端Vue的开源APP解决方案。目前,如果直接使用Weex框架开发应用会存在很多痛点,诸如初始化启动的环境问题、项目工程化…

x64 assembler fun-facts(转载)

原文地址 While implementing the x64 built-in assembler for Delphi 64bit, I got to “know” the AMD64/EM64T architecture a lot more. The good thing about the x64 architecture is that it really builds on the existing instruction format and design. However, …

PHPMailer出现SMTP connect() failed.

很可能是端口问题,最好把$mailer->SMTPSecure和$mailer->Port分别设置为ssl与465或者tls与587,否则某些浏览器不接受不安全的链接,导致$mailer->send()时非常慢,从而导致SMTP connect() failed(我最初就是没有…

Linux系统的根目录下主要包括哪些文件夹,各自的作用

/boot: 系统启动相关的文件,如内核、initrd,以及grub(bootloader)/dev: 设备文件设备文件:块设备:随机访问,数据块字符设备:线性访问,按字符为单位设备号:主设备号(major…

VIM系统复制粘贴

1 需求 系统复制粘贴主要是满足下面两个需求。 在多个对象之间复制粘贴 vim窗口与vim窗口之间外部界面与vim窗口之间不变复制粘贴。从外部界面复制粘贴到vim窗口时,文本不发生任何变化。2 vim寄存器 2.1 寄存器介绍 不得不介绍以下vim寄存器,它是复制粘贴…