淺談BeanUtils的拷貝,深度克隆
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 |
|
兩個物件的雜湊碼不相等,引用物件的地址也不相同,並且對包裝物件的操作都是互不影響,簡單測試下可以看到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,而且物件的雜湊嗎指向了同一位置。
顯而易見,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 |
|