淺談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方法進行賦值

相關文章
相關標籤/搜索