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 |
|