如何“合并”同一个类的两个对象

时间:2017-08-29 11:29:45

标签: java groovy

代码是常规的,但答案可以是Groovy或Java。 我有一个包含此字段的Person类:

class Person(){
String name
String lasName
}

我有一个从同一个类返回两个对象的方法。一个对象包含一些字段,另一个包含其余字段,在我的示例中它将是这样的:

person1 = "name : Jon"
person2 = "lastName : Snow"

我需要的是将person1的所有空字段替换为person2字段,如果这不是null,在我们的示例中,输出将为:

person1.merge(person2)
person1= "name : Jon, lastName : Snow"

Java或Groovy上是否有任何方法可以在不编写所有字段(使用某种循环)的情况下执行类似的操作?

如果没有任何默认方法可供使用,我如何迭代一个类中的所有字段?

5 个答案:

答案 0 :(得分:2)

刚刚使用反射测试。所需的输出是

merged person:Person{name=John, lastName=Snow}     



public static void testReflection() {
        Person p1 = new Person("John", null);
        Person p2 = new Person(null, "Snow");
        Person merged = (Person) mergePersons(p1, p2);
        System.out.println("merged person:" + merged);
}

public static Object mergePersons(Object obj1, Object obj2) throws Exception {
    Field[] allFields = obj1.getClass().getDeclaredFields();
    for (Field field : allFields) {
        if (Modifier.isPublic(field.getModifiers()) && field.isAccessible() && field.get(obj1) == null && field.get(obj2) != null) {
            field.set(obj1, field.get(obj2));
        }
    }
    return obj1;
}

mergePersons接受两个对象。

然后它遍历所有字段并验证第一个对象是否具有空值。 如果是,则验证第二个对象是否未被清零。

如果这是真的,则将值分配给第一个Object。

提供此解决方案只能访问公共数据。如果您还想访问私人数据,则需要删除修改器验证并设置是否可访问,如下所示:

public static Object mergePersons(Object obj1, Object obj2) throws Exception {
    Field[] allFields = obj1.getClass().getDeclaredFields();
    for (Field field : allFields) {

        if (!field.isAccessible() && Modifier.isPrivate(field.getModifiers())) 
            field.setAccessible(true);
        if (field.get(obj1) == null && field.get(obj2) != null) {
            field.set(obj1, field.get(obj2));
        }
    }
    return obj1;
}

答案 1 :(得分:2)

鉴于Groovy字段是作为带有支持字段的getter / setter对实现的,你可以在Groovy中这样做:

static <T> void merge(T from, T to) {
    from.metaClass.properties.findAll { p ->
        p.getProperty(to) == null &&
            p.getProperty(from) != null &&
            to.respondsTo(MetaProperty.getSetterName(p.name))
    }
    .each {
        p -> p.setProperty(to, p.getProperty(from))
    }
}

答案 2 :(得分:1)

你必须走反射路线。我假设您有一个默认构造函数,否则以下内容无法正常工作。此外,它需要两种相同的类型。

public static <T> T mergeObjects(T first, T second) throws IllegalAccessException, InstantiationException {
        Class<?> clazz = first.getClass();
        Field[] fields = clazz.getDeclaredFields();
        Object returnValue = clazz.newInstance();
        for (Field field : fields) {
            field.setAccessible(true);
            Object value1 = field.get(first);
            Object value2 = field.get(second);
            Object value = (value1 != null) ? value1 : value2;
            field.set(returnValue, value);
        }
        return (T) returnValue;
    }

这是示例

import java.lang.reflect.Field;
public class Merge2Obj {

    private String name;
    private String lasName;

    public Merge2Obj() {
        super();
    }

    public Merge2Obj(String name, String lasName) {
        super();
        this.name = name;
        this.lasName = lasName;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getLasName() {
        return lasName;
    }

    public void setLasName(String lasName) {
        this.lasName = lasName;
    }

    public static <T> T mergeObjects(T first, T second) throws IllegalAccessException, InstantiationException {
        Class<?> clazz = first.getClass();
        Field[] fields = clazz.getDeclaredFields();
        Object returnValue = clazz.newInstance();
        for (Field field : fields) {
            field.setAccessible(true);
            Object value1 = field.get(first);
            Object value2 = field.get(second);
            Object value = (value1 != null) ? value1 : value2;
            field.set(returnValue, value);
        }
        return (T) returnValue;
    }

     public static void main(String[] args) throws IllegalAccessException, InstantiationException {
        Merge2Obj obj1 = new Merge2Obj("ABC", null);
        Merge2Obj obj2 = new Merge2Obj("PQR", "LMN");

        Merge2Obj obj3 = mergeObjects(obj1, obj2);

        System.out.println(obj3.name);
        System.out.println(obj3.lasName);
    }

}

答案 3 :(得分:1)

这是一种快速(且冒昧)的方法,与使用字段上的反射基本相同,但使用:

  1. Groovy在java.lang.Object上的内置getProperties() method,它为我们提供了一个属性名称和值的Map
  2. Groovy的default Map constructor,它允许在给定属性Map的情况下创建Object的实例。
  3. 鉴于这两个功能,您可以将要合并的每个对象描述为其属性的地图,去掉null - 值条目,将地图组合在一起(并删除讨厌的“类”条目,是readonly),并使用合并的Map来构建合并的实例。

    class Person {
        String first, last, middle
    }
    
    def p1 = new Person(first: 'bob')
    def p2 = new Person(last: 'barker')
    
    Person merged = (p1.properties.findAll { k, v -> v }  // p1's non-null properties
                   + p2.properties.findAll { k, v -> v }) // plus p2's non-null properties
                   .findAll { k, v -> k != 'class' }      // excluding the 'class' property
    
    assert merged.first == 'bob'
    assert merged.last == 'barker'
    assert merged.middle == null
    

答案 4 :(得分:1)

假设有一个带有getter和setter的可变数据类,Apache BeanUtils可能适合您的需求。

默认情况下,BeanUtilBeansBean.copyProperties(Object dest, Object orig)会查找T orig.get*()dest.set*(T value)对,并使用前者的结果调用后者。

但是您可以注入自定义PropertyUtilsBean,因此您可以包装默认值以防止它替换非空属性:

public NoClobberPropertyUtilsBean extends PropertyUtilsBean {
     @Override
     public void setSimpleProperty((Object bean,
                          String name,
                          Object value)
                   throws IllegalAccessException,
                          InvocationTargetException,
                          NoSuchMethodException {
          if(getProperty(bean,name) == null) {
              super.setSimpleProperty(bean,name,value);
          }
     }
}

现在您可以合并:

BeanUtilsBean beanUtils = new BeanUtilsBean(new ConvertUtilsBean(), new NoClobberPropertyUtilsBean());
Person merged = new Person();
beanUtils.copyProperties(person1);
beanUtils.copyProperties(person2);

如果两个来源中的属性都为非空,则第一个copyProperties获胜。

你当然可以改变语义,例如,如果守卫是if(value != null),它会表现得不同。

在某个级别,BeanUtils只是其他答案所暗示的反射操作的包装器。是否需要额外的抽象级别取决于您。如果要支持map / list成员或BeanUtils'DynaBean类,可能需要覆盖更多方法。