轉載自:https://www.cnblogs.com/tison/p/7840647.html
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接口。
|

