浅谈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()); }
打印结果:
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());
//打印测试结果 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、BeanUtils的copyProperties()方法并不是完全的深度克隆,在包含有引用类型的对象拷贝上就可能会出现引用对象指向同一个的情况,且该方法的性能低下,项目中一定要谨慎使用。
2、要实现高性能且安全的深度克隆方法还是实现Serializable接口,多层克隆时,引用类型均要实现Serializable接口。