浅谈BeanUtils的拷贝,深度克隆

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

复制代码

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方法此处省略
}

复制代码

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

复制代码

@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());
    }

复制代码

打印结果:安全

1mybatis

2工具

3性能

4测试

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

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

src.main.mybatis.User@7907ec20代理

src.main.mybatis.User@546a03af

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

二、项目测试
可是到了本人所作的项目中,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接口。

三、深拷贝
深复制(深克隆)被复制对象的全部变量都含有与原来的对象相同的值,除去那些引用其余对象的变量,那些引用其余对象的变量将指向被复制过的新对象,而再也不试原有的那些被引用的对象,换言之,深复制把要复制的对象所引用的对象都复制了一遍。
把对象写到流里的过程是串行化(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方法进行赋值

相关文章
相关标签/搜索