对象拷贝
对象拷贝分为深拷贝和浅拷贝。根据使用场景进行不同选择。在Java中,数据类型分为值类型(基本数据类型)和引用类型,值类型包括int、double、byte、boolean、char等简单数据类型,引用类型包括类、接口、数组等复杂类型。
深度拷贝和浅度拷贝的主要区别在于是否支持引用类型的属性拷贝,本文将探讨目前使用较多的几种对象拷贝的方案,以及其是否支持深拷贝和性能对比。
首先来创建两个测试bean
注:一定要保证有set/get方法,成员变量必须要同名
@Data
public class User1 {
String name;
String password;
String phone;
}
@Data
public class User2 {
String name;
String password;
String phone;
}
1.Spring的BeanUtils(简单易用)
org.springframework.beans.BeanUtils
BeanUtils.copyProperties(源对象,目标对象)
测试方法:
public static void main(String[] args){
User1 user1=new User1();
user1.setName("user1_name");
user1.setPassword("user1_password");
user1.setPhone("user1_phone");
User2 user2=new User2();
BeanUtils.copyProperties(user1,user2);
System.out.println(user2.toString());
}
执行结果:User2(name=user1_name, password=user1_password, phone=user1_phone)
实现原理
BeanUtils部分源码分析
public abstract class BeanUtils {
public static void copyProperties(Object source, Object target) throws BeansException {
copyProperties(source, target, (Class)null, (String[])null);
}
private static void copyProperties(Object source, Object target, @Nullable Class> editable, @Nullable String... ignoreProperties) throws BeansException {
Assert.notNull(source, "Source must not be null");
Assert.notNull(target, "Target must not be null");
Class> actualEditable = target.getClass();
if (editable != null) {
if (!editable.isInstance(target)) {
throw new IllegalArgumentException("Target class [" + target.getClass().getName() + "] not assignable to Editable class [" + editable.getName() + "]");
}
actualEditable = editable;
}
PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);
List ignoreList = ignoreProperties != null ? Arrays.asList(ignoreProperties) : null;
PropertyDescriptor[] var7 = targetPds;
int var8 = targetPds.length;
for(int var9 = 0; var9 < var8; ++var9) {
PropertyDescriptor targetPd = var7[var9];
Method writeMethod = targetPd.getWriteMethod();
if (writeMethod != null && (ignoreList == null || !ignoreList.contains(targetPd.getName()))) {
PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName());
if (sourcePd != null) {
Method readMethod = sourcePd.getReadMethod();
if (readMethod != null && ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())) {
try {
if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
readMethod.setAccessible(true);
}
Object value = readMethod.invoke(source);
if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
writeMethod.setAccessible(true);
}
writeMethod.invoke(target, value);
} catch (Throwable var15) {
throw new FatalBeanException("Could not copy property '" + targetPd.getName() + "' from source to target", var15);
}
}
}
}
}
}
``````
}
实现的方式很简单,就是对两个对象中相同名字的属性进行简单get/set,仅检查属性的可访问性。可以看到, 成员变量赋值是基于目标对象的成员列表, 并且会跳过ignore的以及在源对象中不存在的, 所以这个方法是安全的, 不会因为两个对象之间的结构差异导致错误
注:必须保证同名的两个成员变量类型相同,同名属性一个是包装类型,一个是非包装类型也是可以的
2.Apache的BeanUtils(拓展性强,相对复杂)
Apache Common BeanUtil是一个常用的在对象之间复制数据的工具类,web开发框架struts就是依赖于它进行ActionForm的创建。
org.apache.commons.beanutils.BeanUtils
BeanUtils.copyProperties(目标对象,源对象)
需要引入依赖
commons-beanutils
commons-beanutils
1.9.3
测试方法:
public static void main(String[] args) throws InvocationTargetException, IllegalAccessException {
User1 user1=new User1();
user1.setName("user1_name");
user1.setPassword("user1_password");
user1.setPhone("user1_phone");
User2 user2=new User2();
BeanUtils.copyProperties(user2,user1);
System.out.println(user2.toString());
}
执行结果:User2(name=user1_name, password=user1_password, phone=user1_phone)
实现原理
BeanUtils部分源码分析
public class BeanUtils {
public static void copyProperties(Object dest, Object orig) throws IllegalAccessException, InvocationTargetException {
BeanUtilsBean.getInstance().copyProperties(dest, orig);
}
public void copyProperties(Object dest, Object orig) throws IllegalAccessException, InvocationTargetException {
if (dest == null) {
throw new IllegalArgumentException("No destination bean specified");
} else if (orig == null) {
throw new IllegalArgumentException("No origin bean specified");
} else {
if (this.log.isDebugEnabled()) {
this.log.debug("BeanUtils.copyProperties(" + dest + ", " + orig + ")");
}
int i;
String name;
Object value;
if (orig instanceof DynaBean) {
DynaProperty[] origDescriptors = ((DynaBean)orig).getDynaClass().getDynaProperties();
for(i = 0; i < origDescriptors.length; ++i) {
name = origDescriptors[i].getName();
if (this.getPropertyUtils().isReadable(orig, name) && this.getPropertyUtils().isWriteable(dest, name)) {
value = ((DynaBean)orig).get(name);
this.copyProperty(dest, name, value);
}
}
} else if (orig instanceof Map) {
Iterator entries = ((Map)orig).entrySet().iterator();
while(entries.hasNext()) {
Entry entry = (Entry)entries.next();
name = (String)entry.getKey();
if (this.getPropertyUtils().isWriteable(dest, name)) {
this.copyProperty(dest, name, entry.getValue());
}
}
} else {
PropertyDescriptor[] origDescriptors = this.getPropertyUtils().getPropertyDescriptors(orig);
for(i = 0; i < origDescriptors.length; ++i) {
name = origDescriptors[i].getName();
if (!"class".equals(name) && this.getPropertyUtils().isReadable(orig, name) && this.getPropertyUtils().isWriteable(dest, name)) {
try {
value = this.getPropertyUtils().getSimpleProperty(orig, name);
this.copyProperty(dest, name, value);
} catch (NoSuchMethodException var7) {
}
}
}
}
}
}
``````
}
commons-beanutils则施加了很多的检验,包括类型的转换,甚至于还会检验对象所属的类的可访问性。BeanUtils能够顺利的完成对象属性值的复制,依赖于其对类型的识别。
除了支持基本类型以及基本类型的数组之外,还支持java.sql.Date,java.sql.Time, java.sql.TimeStamp, java.io.File, javaio.URL这些类的对象,其余一概不支持。不过可以自定义Converter。然后注册进去。在org.apache.commons.beanutils.converters包中有一系列converter类,用于不同类型之间对象的转化。主要通过注入新的类型转换器,因为默认情况下,BeanUtils对复杂对象的复制是引用
注:commons-beanutils中的装换是不支持java.util.Date的。
总结:关于bean复制,如果属性较少,建议直接写个方法完成get/set即可。如果属性较多,可以自己采用反射实现一个满足自己需要的工具类,或者使用BeanUtils类。BeanUtils是利用反射机制对JavaBean的属性进行处理。一个JavaBean通常包含了大量的属性,很多情况下,对JavaBean的处理导致大量get/set代码堆积,增加了代码长度和阅读代码的难度。由于这些BeanUtils类都是采用反射机制实现的,对程序的效率会有影响。