通过直接字段访问复制对象属性



是否有一种简单的方法可以使用直接字段访问将对象的属性复制到具有相同字段名的不同类的另一个对象上,即当其中一个类没有字段的getter或setter时?当它们都有getter和setter方法时,我可以使用org.springframework.beans.BeanUtils#copyProperties(Object source, Object target),但当它们没有时,我该怎么办?

字段是公开的也可能是相关的。

我知道我可以使用反射编写自己的代码来实现这一点,但我希望有一些库可以提供一行代码。

我没有找到一个第三方库来完成我想要的任务。我会把我的代码粘贴在这里,以防对任何人有用:

import java.lang.reflect.Field;
import java.util.AbstractMap;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
 * An alternative to Spring's BeanUtils#copyProperties for classes that don't have getters and setters.
 */
public class FieldCopier {
    private static final Logger log = LoggerFactory.getLogger(FieldCopier.class);
    /** Always use the same instance, so that we can cache the fields. */
    private static final FieldCopier instance = new FieldCopier();
    /** Caching the paired fields cuts the time taken by about 25% */
    private final Map<Map.Entry<Class<?>, Class<?>>, Map<Field, Field>> PAIRED_FIELDS = new ConcurrentHashMap<>();
    /** Caching the fields cuts the time taken by about 50% */
    private final Map<Class<?>, Field[]> FIELDS = new ConcurrentHashMap<>();
    public static FieldCopier instance() {
        return instance;
    }
    private FieldCopier() {
        // do not instantiate
    }
    public <S, T> T copyFields(S source, T target) {
        Map<Field, Field> pairedFields = getPairedFields(source, target);
        for (Field sourceField : pairedFields.keySet()) {
            Field targetField = pairedFields.get(sourceField);
            try {
                Object value = getValue(source, sourceField);
                setValue(target, targetField, value);
            } catch(Throwable t) {
                throw new RuntimeException("Failed to copy field value", t);
            }
        }
        return target;
    }
    private <S, T> Map<Field, Field> getPairedFields(S source, T target) {
        Class<?> sourceClass = source.getClass();
        Class<?> targetClass = target.getClass();
        Map.Entry<Class<?>, Class<?>> sourceToTarget = new AbstractMap.SimpleImmutableEntry<>(sourceClass, targetClass);
        PAIRED_FIELDS.computeIfAbsent(sourceToTarget, st -> mapSourceFieldsToTargetFields(sourceClass, targetClass));
        Map<Field, Field> pairedFields = PAIRED_FIELDS.get(sourceToTarget);
        return pairedFields;
    }
    private Map<Field, Field> mapSourceFieldsToTargetFields(Class<?> sourceClass, Class<?> targetClass) {
        Map<Field, Field> sourceFieldsToTargetFields = new HashMap<>();
        Field[] sourceFields = getDeclaredFields(sourceClass);
        Field[] targetFields = getDeclaredFields(targetClass);
        for (Field sourceField : sourceFields) {
            if (sourceField.getName().equals("serialVersionUID")) {
                continue;
            }
            Field targetField = findCorrespondingField(targetFields, sourceField);
            if (targetField == null) {
                log.warn("No target field found for " + sourceField.getName());
                continue;
            }
            if (Modifier.isFinal(targetField.getModifiers())) {
                log.warn("The target field " + targetField.getName() + " is final, and so cannot be written to");
                continue;
            }
            sourceFieldsToTargetFields.put(sourceField, targetField);
        }
        return Collections.unmodifiableMap(sourceFieldsToTargetFields);
    }
    private Field[] getDeclaredFields(Class<?> clazz) {
        FIELDS.computeIfAbsent(clazz, Class::getDeclaredFields);
        return FIELDS.get(clazz);
    }
    private <S> Object getValue(S source, Field sourceField) throws IllegalArgumentException, IllegalAccessException {
        sourceField.setAccessible(true);
        return sourceField.get(source);
    }
    private <T> void setValue(T target, Field targetField, Object value) throws IllegalArgumentException, IllegalAccessException {
        targetField.setAccessible(true);
        targetField.set(target, value);
    }
    private Field findCorrespondingField(Field[] targetFields, Field sourceField) {
        for (Field targetField : targetFields) {
            if (sourceField.getName().equals(targetField.getName())) {
                if (sourceField.getType().equals(targetField.getType())) {
                    return targetField;
                } else {
                    log.warn("Different types for field " +  sourceField.getName() 
                            + " source " + sourceField.getType() + " and target " + targetField.getType());
                    return null;
                }
            }
        }
        return null;
    }
}

为此编写一个简单的实用程序类,您就得到了一行。。。这个任务是IMHO容易使用的一个库。

请记住,如果默认情况下没有字段,请使其可访问。以下是您可以从我们的代码库中调整的两个功能:

public void injectIntoObject(对象o,对象值){尝试{getField().set(o,value);}catch(非法争论异常e){抛出新的RuntimeException("在将对象'"+o+"'中的类'"+beanDef.getName()+"'的属性'"+name+"'注入到'"+value+"'时出现非法参数。得到了一个类型为"+value.getClass().getCanonicalName()"的参数,但需要一个类型"+type.getCanonialName(!",e);}catch(非法访问异常e){getField().setAccessible(true);尝试{getField().set(o,value);}catch(非法争论异常e1){抛出新的RuntimeException("在将对象'"+o+"'中的类'"+beanDef.getName()+"'的属性'"+name+"'注入到'"+value+"'时出现非法参数。得到了一个类型为"+value.getClass().getCanonicalName()"的参数,但需要一个类型"+type.getCanonialName(!",e);}catch(非法访问异常e1){throw new RuntimeException("将对象'"+o+"'中的类'"+beanDef.getName()+"'的属性'"+name+"'注入到'"+value+"'!"时发生访问异常,e);}}catch(异常e){throw new RuntimeException("将对象'"+o+"'中的类'"+beanDef.getName()+"'的属性'"+name+"'设置为'"+value+"'!"时发生异常,e);}}公共对象extractFromObject(对象o){尝试{return getField().get(o);}catch(非法争论异常e){throw new RuntimeException("在读取对象"+o+"中类"+beanDef.getName()+"的属性"+name+"时非法参数,但需要"+type.getCanonicalName()"+"!"中的一个,e);}catch(非法访问异常e){getField().setAccessible(true);尝试{return getField().get(o);}catch(非法争论异常e1){throw new RuntimeException("在读取对象"+o+"中类"+beanDef.getName()+"的属性"+name+"时非法参数,但需要"+type.getCanonicalName()"+"!"中的一个,e);}catch(非法访问异常e1){throw new RuntimeException("读取对象'"+o+"'中的类'"+beanDef.getName()+"'的属性'"+name+"'时发生访问异常!",e);}}catch(异常e){throw new RuntimeException("读取对象'"+o+"'中的类'"+beanDef.getName()+"'的属性'"+name+"'时发生异常!",e);}}

getField()返回一个java.lang.Field,应该很容易实现。

我强烈建议您避免对此使用反射,因为它会导致代码难以理解和维护。(反射可以用于测试,在创建框架时,除此之外,它可能会产生比解决更多的问题。)

此外,如果对象的属性需要由对象以外的其他对象访问,则它需要一个非私有的作用域(或非私有的访问器/getter)。这就是变量作用域的全部意义。在没有访问器的情况下保持变量私有,然后通过反射以任何方式使用它是错误的,只会导致问题,因为您正在创建对读者撒谎的代码。

public class MyClass {
    private Integer someInt;
    private String someString;
    private List<Double> someList;
    //...
}
public class MyOtherClass {
    private Integer someInt;
    private String someString;
    private List<Double> someList;
    private boolean somethingElse;
    public copyPropertiesFromMyClass(final MyClass myClass) {
        this.someInt = myClass.getSomeInt();
        this.someString = myClass.getSomeString();
        this.someList = new ArrayList<>(myClass.getSomeList());
    }
}

最新更新