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

时间:2015-12-14 09:09:10

标签: java

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

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

我知道我可以使用反射编写我自己的代码来执行此操作,但我希望有一些提供单行的库。

3 个答案:

答案 0 :(得分:3)

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

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

答案 1 :(得分:1)

为此编写一个简单的实用程序类,你得到了一个班轮......这个任务是恕我直言,很容易使用它的库。

请注意,如果默认情况下不允许您访问字段。以下是您可以从我们的代码库中调整的两个函数:

public void injectIntoObject(Object o, Object value) {
    try {
        getField().set(o, value);
    } catch (IllegalArgumentException e) {
        throw new RuntimeException("Illegal argument while injecting property '"+name+"' of class '"+beanDef.getName()+"' in object '"+o+"' to '"+value+"'. Got one of type "+value.getClass().getCanonicalName()+" but needed one of "+type.getCanonicalName()+"!",e);
    } catch (IllegalAccessException e) {
        getField().setAccessible(true);
        try {
            getField().set(o, value);
        } catch (IllegalArgumentException e1) {
            throw new RuntimeException("Illegal argument while injecting property '"+name+"' of class '"+beanDef.getName()+"' in object '"+o+"' to '"+value+"'. Got one of type "+value.getClass().getCanonicalName()+" but needed one of "+type.getCanonicalName()+"!",e);
        } catch (IllegalAccessException e1) {
            throw new RuntimeException("Access exception while injecting property '"+name+"' of class '"+beanDef.getName()+"' in object '"+o+"' to '"+value+"'!",e);
        }
    } catch (Exception e) {
        throw new RuntimeException("Exception while setting property '"+name+"' of class '"+beanDef.getName()+"' in object '"+o+"' to '"+value+"'!",e);
    }
}

public Object extractFromObject(Object o)  {
    try {
       return getField().get(o);
    } catch (IllegalArgumentException e) {
        throw new RuntimeException("Illegal argument while read property '"+name+"' of class '"+beanDef.getName()+"' in object '"+o+"'  but needed one of "+type.getCanonicalName()+"!",e);
    } catch (IllegalAccessException e) {
        getField().setAccessible(true);
        try {
            return getField().get(o);
        } catch (IllegalArgumentException e1) {
            throw new RuntimeException("Illegal argument while read property '"+name+"' of class '"+beanDef.getName()+"' in object '"+o+"' but needed one of "+type.getCanonicalName()+"!",e);
        } catch (IllegalAccessException e1) {
            throw new RuntimeException("Access exception while read property '"+name+"' of class '"+beanDef.getName()+"' in object '"+o+"'!",e);
        }
    } catch (Exception e) {
        throw new RuntimeException("Exception while read property '"+name+"' of class '"+beanDef.getName()+"' in object '"+o+"'!",e);
    }

}

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

答案 2 :(得分:0)

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

此外,如果对象的属性需要由对象以外的其他东西访问,则它需要非私有的范围(或非私有的访问者/获取者)。这是变量范围的重点。保持一个没有访问器的变量私有,然后通过反射使用它是错误的,并且只会导致问题,因为你正在创建代表读者的代码。

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());
    }
}