编程过程中,有些时候某些不同的类有相同的字段,可能会遇到同时给他们赋值的情况。简单的时候,可以写重复的代码,重复太多的时候,就不如给一个赋值完,直接复制给另一个对象。
上面这种情况在数据库的实体类中比较常见。
java对象之间属性值复制在许多开源框架中也有实现,在这里介绍自实现、Spring、apache commons-beanutils三种实现方式。
一.自己实现简单的对象属性复制
CopyUtil01.java
package com.bijian.study;
import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
/*
* 复制的对象必须同时拥有setter和getter方法,只有一个的时候会报异常,都没有的时候就不复制
*/
public class CopyUtil01 {
public static void Copy(Object source, Object dest) throws Exception {
//获取属性
BeanInfo sourceBean = Introspector.getBeanInfo(source.getClass(), java.lang.Object.class);
PropertyDescriptor[] sourceProperty = sourceBean.getPropertyDescriptors();
BeanInfo destBean = Introspector.getBeanInfo(dest.getClass(), java.lang.Object.class);
PropertyDescriptor[] destProperty = destBean.getPropertyDescriptors();
try {
for (int i = 0; i < sourceProperty.length; i++) {
for (int j = 0; j < destProperty.length; j++) {
if (sourceProperty[i].getName().equals(destProperty[j].getName())) {
//调用source的getter方法和dest的setter方法
destProperty[j].getWriteMethod().invoke(dest, sourceProperty[i].getReadMethod().invoke(source));
break;
}
}
}
} catch (Exception e) {
throw new Exception("属性复制失败:" + e.getMessage());
}
}
}
CopyUtil02.java
package com.bijian.study;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import java.util.Set;
/*
* 该方法接收两个参数,一个是复制的源对象——要复制的对象,一个是复制的目标对象——对象副本。
* 当然这个方法也可以在两个不同对象间使用,这时候只要目标对象和对象具有一个或多个相同类型及名称的属性,那么就会把源对象的属性值赋给目标对象的属性
*/
public class CopyUtil02 {
public static <T> T getBean(T TargetBean, T SourceBean) throws Exception {
if (TargetBean == null)
return null;
Field[] tFields = TargetBean.getClass().getDeclaredFields();
Field[] sFields = SourceBean.getClass().getDeclaredFields();
try {
for (Field field : tFields) {
String fieldName = field.getName();
if (fieldName.equals("serialVersionUID"))
continue;
if (field.getType() == Map.class)
continue;
if (field.getType() == Set.class)
continue;
if (field.getType() == List.class)
continue;
for (Field sField : sFields) {
if (!sField.getName().equals(fieldName)) {
continue;
}
Class type = field.getType();
String setName = getSetMethodName(fieldName);
Method tMethod = TargetBean.getClass().getMethod(setName, new Class[] { type });
String getName = getGetMethodName(fieldName);
Method sMethod = SourceBean.getClass().getMethod(getName, null);
Object setterValue = sMethod.invoke(SourceBean, null);
tMethod.invoke(TargetBean, new Object[] { setterValue });
}
}
} catch (Exception e) {
throw new Exception("设置参数信息发生异常", e);
}
return TargetBean;
}
private static String getGetMethodName(String fieldName) {
fieldName = replaceFirstCharToUpper(fieldName);
return "get" + fieldName;
}
private static String getSetMethodName(String fieldName) {
fieldName = replaceFirstCharToUpper(fieldName);
return "set" + fieldName;
}
private static String replaceFirstCharToUpper(String fieldName) {
char[] chars = new char[1];
chars[0] = fieldName.charAt(0);
String temp = new String(chars);
if (chars[0] >= 'a' && chars[0] <= 'z') {
fieldName = fieldName.replaceFirst(temp, temp.toUpperCase());
}
return fieldName;
}
}
CopyUtil03.java
package com.bijian.study;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
/*
* 采用反射,通过源对象getter 方法获取属性值,并通过目标对象的setter方法设置到目标对象中去
*/
public class CopyUtil03 {
/**
* 利用反射实现对象之间属性复制
* @param from
* @param to
*/
public static void copyProperties(Object from, Object to) throws Exception {
copyPropertiesExclude(from, to, null);
}
/**
* 复制对象属性
* @param from
* @param to
* @param excludsArray 排除属性列表
* @throws Exception
*/
@SuppressWarnings("unchecked")
public static void copyPropertiesExclude(Object from, Object to, String[] excludsArray) throws Exception {
List<String> excludesList = null;
if(excludsArray != null && excludsArray.length > 0) {
excludesList = Arrays.asList(excludsArray); //构造列表对象
}
Method[] fromMethods = from.getClass().getDeclaredMethods();
Method[] toMethods = to.getClass().getDeclaredMethods();
Method fromMethod = null, toMethod = null;
String fromMethodName = null, toMethodName = null;
for (int i = 0; i < fromMethods.length; i++) {
fromMethod = fromMethods[i];
fromMethodName = fromMethod.getName();
if (!fromMethodName.contains("get"))
continue;
//排除列表检测
if(excludesList != null && excludesList.contains(fromMethodName.substring(3).toLowerCase())) {
continue;
}
toMethodName = "set" + fromMethodName.substring(3);
toMethod = findMethodByName(toMethods, toMethodName);
if (toMethod == null)
continue;
Object value = fromMethod.invoke(from, new Object[0]);
if(value == null)
continue;
//集合类判空处理
if(value instanceof Collection) {
Collection newValue = (Collection)value;
if(newValue.size() <= 0)
continue;
}
toMethod.invoke(to, new Object[] {value});
}
}
/**
* 对象属性值复制,仅复制指定名称的属性值
* @param from
* @param to
* @param includsArray
* @throws Exception
*/
@SuppressWarnings("unchecked")
public static void copyPropertiesInclude(Object from, Object to, String[] includsArray) throws Exception {
List<String> includesList = null;
if(includsArray != null && includsArray.length > 0) {
includesList = Arrays.asList(includsArray); //构造列表对象
} else {
return;
}
Method[] fromMethods = from.getClass().getDeclaredMethods();
Method[] toMethods = to.getClass().getDeclaredMethods();
Method fromMethod = null, toMethod = null;
String fromMethodName = null, toMethodName = null;
for (int i = 0; i < fromMethods.length; i++) {
fromMethod = fromMethods[i];
fromMethodName = fromMethod.getName();
if (!fromMethodName.contains("get"))
continue;
//排除列表检测
String str = fromMethodName.substring(3);
if(!includesList.contains(str.substring(0,1).toLowerCase() + str.substring(1))) {
continue;
}
toMethodName = "set" + fromMethodName.substring(3);
toMethod = findMethodByName(toMethods, toMethodName);
if (toMethod == null)
continue;
Object value = fromMethod.invoke(from, new Object[0]);
if(value == null)
continue;
//集合类判空处理
if(value instanceof Collection) {
Collection newValue = (Collection)value;
if(newValue.size() <= 0)
continue;
}
toMethod.invoke(to, new Object[] {value});
}
}
/**
* 从方法数组中获取指定名称的方法
*
* @param methods
* @param name
* @return
*/
public static Method findMethodByName(Method[] methods, String name) {
for (int j = 0; j < methods.length; j++) {
if (methods[j].getName().equals(name))
return methods[j];
}
return null;
}
}
O1.java
package com.bijian.study;
public class O1 {
private String aac001;
private String aac002;
private Double ddd001;
public String getAac001() {
return aac001;
}
public void setAac001(String aac001) {
this.aac001 = aac001;
}
public String getAac002() {
return aac002;
}
public void setAac002(String aac002) {
this.aac002 = aac002;
}
public Double getDdd001() {
return ddd001;
}
public void setDdd001(Double ddd001) {
this.ddd001 = ddd001;
}
}
O2.java
package com.bijian.study;
public class O2 {
private String aac001;
private String aac002;
private Double ddd001;
private String aac003;
private String aac004;
public String getAac002() {
return aac002;
}
public void setAac002(String aac002) {
this.aac002 = aac002;
}
public String getAac001() {
return aac001;
}
public void setAac001(String aac001) {
this.aac001 = aac001;
}
public String getAac003() {
return aac003;
}
public void setAac003(String aac003) {
this.aac003 = aac003;
}
public String getAac004() {
return aac004;
}
public void setAac004(String aac004) {
this.aac004 = aac004;
}
public Double getDdd001() {
return ddd001;
}
public void setDdd001(Double ddd001) {
this.ddd001 = ddd001;
}
}
CopyJunitTest.java
package com.bijian.study;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
public class CopyJunitTest {
O1 o1 = null;
O2 o2 = null;
@Before
public void setUp() {
this.o1 = new O1();
this.o2 = new O2();
this.o1.setAac001("00007");
this.o1.setAac002("国产零零零零七");
this.o1.setDdd001(Double.valueOf(3.141592653589793D));
}
@Test
public void test_copyUtil01() {
try {
CopyUtil01.Copy(this.o1, this.o2);
System.out.println("CopyUtil01单元测试");
System.out.println("-------------源对象o1----------------");
showObjectPropertyValue(this.o1);
System.out.println("-------------目标对象o2----------------");
showObjectPropertyValue(this.o2);
System.out.println();
} catch (Exception e) {
Assert.fail();
}
}
@Test
public void test_copyUtil02() {
try {
this.o2 = ((O2) CopyUtil02.getBean(this.o2, this.o1));
System.out.println("CopyUtil02单元测试");
System.out.println("-------------源对象o1----------------");
showObjectPropertyValue(this.o1);
System.out.println("-------------目标对象o2----------------");
showObjectPropertyValue(this.o2);
System.out.println();
} catch (Exception e) {
Assert.fail();
}
}
@Test
public void test_copyUtil03_copyProperties() {
try {
CopyUtil03.copyProperties(this.o1, this.o2);
System.out.println("CopyUtil03的copyProperties方法单元测试");
System.out.println("-------------源对象o1----------------");
showObjectPropertyValue(this.o1);
System.out.println("-------------目标对象o2----------------");
showObjectPropertyValue(this.o2);
System.out.println();
} catch (Exception e) {
Assert.fail();
}
}
@Test
public void test_copyUtil03_copyPropertiesExclude() {
try {
String[] excludsArray = new String[1];
excludsArray[0] = "aac001";
CopyUtil03.copyPropertiesExclude(this.o1, this.o2, excludsArray);
System.out.println("CopyUtil03的copyPropertiesExclude方法单元测试");
System.out.println("-------------源对象o1----------------");
showObjectPropertyValue(this.o1);
System.out.println("-------------目标对象o2----------------");
showObjectPropertyValue(this.o2);
System.out.println();
} catch (Exception e) {
Assert.fail();
}
}
@Test
public void test_copyUtil03_copyPropertiesInclude() {
try {
String[] includsArray = new String[2];
includsArray[0] = "aac001";
includsArray[1] = "ddd001";
CopyUtil03.copyPropertiesInclude(this.o1, this.o2, includsArray);
System.out.println("CopyUtil03的copyPropertiesInclude方法单元测试");
System.out.println("-------------源对象o1----------------");
showObjectPropertyValue(this.o1);
System.out.println("-------------目标对象o2----------------");
showObjectPropertyValue(this.o2);
} catch (Exception e) {
Assert.fail();
}
}
private void showObjectPropertyValue(Object o) throws Exception {
for (PropertyDescriptor property : Introspector.getBeanInfo(
o.getClass(), Object.class).getPropertyDescriptors())
System.out.println(property.getReadMethod()
.invoke(o, new Object[0]));
}
}
单元测试输出结果:
CopyUtil01单元测试 -------------源对象o1---------------- 00007 国产零零零零七 3.141592653589793 null -------------目标对象o2---------------- 00007 国产零零零零七 null null 3.141592653589793 CopyUtil02单元测试 -------------源对象o1---------------- 00007 国产零零零零七 3.141592653589793 null -------------目标对象o2---------------- 00007 国产零零零零七 null null 3.141592653589793 CopyUtil03的copyProperties方法单元测试 -------------源对象o1---------------- 00007 国产零零零零七 3.141592653589793 null -------------目标对象o2---------------- 00007 国产零零零零七 null null 3.141592653589793 CopyUtil03的copyPropertiesExclude方法单元测试 -------------源对象o1---------------- 00007 国产零零零零七 3.141592653589793 null -------------目标对象o2---------------- null 国产零零零零七 null null 3.141592653589793 CopyUtil03的copyPropertiesInclude方法单元测试 -------------源对象o1---------------- 00007 国产零零零零七 3.141592653589793 null -------------目标对象o2---------------- 00007 null null null 3.141592653589793如上方式都是通过java的反射机制复制对象,不支持深度复制以及复制集合类型,但通用性会提高很多。 java本身提供了对象复制的能力,在java.lang.Object类中有clone方法,该方法是一个protected方法,在子类需要重写此方法并声明为public类型,而且还需实现Cloneable接口才能提供对象复制的能力,clone()是一个native方法,native方法的效率一般来说都是远高于java中的非native方法,对性能比较关心的话应考虑这种方式。
二.利用Spring实现属性之间的复制
spring内部自有实现方法,如果我们需要在外面采用spring的托管复制,需要修改spring的源码,将spring中的org.springframework.beans.CachedIntrospectionResults类的forClass、getPropertyDescriptor、getBeanInfo改为可见的后重新打包。然后将Spring中关于复制的代码提取出来,最后修改成代码如下:
/**
* 利用spring实现bean之间属性复制
* @param source
* @param target
*/
@SuppressWarnings("unchecked")
public static void copyPropertiesBySpring(Object source, Object target) throws Exception {
Class actualEditable = target.getClass();
PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);
for (int i = 0; i < targetPds.length; i++) {
PropertyDescriptor targetPd = targetPds[i];
if (targetPd.getWriteMethod() != null) {
PropertyDescriptor sourcePd = getPropertyDescriptor(source
.getClass(), targetPd.getName());
if (sourcePd != null && sourcePd.getReadMethod() != null) {
try {
Method readMethod = sourcePd.getReadMethod();
if (!Modifier.isPublic(readMethod.getDeclaringClass()
.getModifiers())) {
readMethod.setAccessible(true);
}
Object value = readMethod.invoke(source, new Object[0]);
if(value == null)
continue;
//集合类判空处理
if(value instanceof Collection) {
// Collection newValue = (Collection)value;
// if(newValue.size() <= 0)
continue;
}
Method writeMethod = targetPd.getWriteMethod();
if (!Modifier.isPublic(writeMethod.getDeclaringClass()
.getModifiers())) {
writeMethod.setAccessible(true);
}
writeMethod.invoke(target, new Object[] { value });
} catch (Throwable ex) {
}
}
}
}
}
/**
* 获取指定类指定名称的属性描述符
* @param clazz
* @param propertyName
* @return
* @throws BeansException
*/
@SuppressWarnings("unchecked")
public static PropertyDescriptor getPropertyDescriptor(Class clazz,
String propertyName) throws BeansException {
CachedIntrospectionResults cr = CachedIntrospectionResults.forClass(clazz);
return cr.getPropertyDescriptor(propertyName);
}
/**
* 获取指定类得所有属性描述符
* @param clazz
* @return
* @throws BeansException
*/
@SuppressWarnings("unchecked")
public static PropertyDescriptor[] getPropertyDescriptors(Class clazz) throws BeansException {
CachedIntrospectionResults cr = CachedIntrospectionResults.forClass(clazz);
return cr.getBeanInfo().getPropertyDescriptors();
}
三.利用apache commons-beanutils的开源实现
BeanUtils.copyProperties(dst, src)。方法能够将源对象和目标对象中相同名称的属性值复制过去。注意的是参数前面的是目标对象,后面是源对象。使用该方法需要注意:不能将入口方法与源对象、目标对象之一放在同一源文件之内,否者将没有任何效果。
PropertyUtils.copyProperties(dst, src)。功能与BeanUtils.copyProperties类似,只是在同属性名称的类型参数之间可以执行转换操作。
BeanUtils.copyProperties(dst, src)。方法能够将源对象和目标对象中相同名称的属性值复制过去。注意的是参数前面的是目标对象,后面是源对象。使用该方法需要注意:不能将入口方法与源对象、目标对象之一放在同一源文件之内,否者将没有任何效果。
PropertyUtils.copyProperties(dst, src)。功能与BeanUtils.copyProperties类似,只是在同属性名称的类型参数之间可以执行转换操作。