在Java中合并两个对象

24

我有两个相同类型的对象。

Class A {
  String a;
  List b;
  int c;
}

A obj1 = new A();
A obj2 = new A();

obj1 => {a = "hello"; b = null; c = 10}
obj2 => {a = null; b = new ArrayList(); c = default value}

你能告诉我将这些对象合并成单个对象的最佳方法吗?

obj3 = {a = "hello"; b = (same arraylist from obj2); c = 10}

2
你能描述一下你期望这个“merged”对象的样子吗? - Asaph
1
如果 obj1.a = "George"obj2.a = "Lucas",那么在“merged”对象中,obj3.a 的值应该是什么? - Asaph
你可以假设这些对象是互斥的。 - Ganesh
10个回答

31

只要您拥有具有自己的getter和setter的POJO,这段代码就可以正常工作。该方法使用来自update中非空值更新obj。它在obj上调用setParameter()并使用update上的getParameter()返回值:

public void merge(Object obj, Object update){
    if(!obj.getClass().isAssignableFrom(update.getClass())){
        return;
    }

    Method[] methods = obj.getClass().getMethods();

    for(Method fromMethod: methods){
        if(fromMethod.getDeclaringClass().equals(obj.getClass())
                && fromMethod.getName().startsWith("get")){

            String fromName = fromMethod.getName();
            String toName = fromName.replace("get", "set");

            try {
                Method toMetod = obj.getClass().getMethod(toName, fromMethod.getReturnType());
                Object value = fromMethod.invoke(update, (Object[])null);
                if(value != null){
                    toMetod.invoke(obj, value);
                }
            } catch (Exception e) {
                e.printStackTrace();
            } 
        }
    }
}

谢谢回答...但是我的对象有很多原始类型,比如int、float等,它还有List作为成员。 - Ganesh
使用obj.getClass().getDeclaredMethods()跳过getClass/setClass等方法,仅获取在该类或相应接口中定义的方法。 - tarkeshwar
@jon 函数 updates obj 参数对象,以便将所有更改保存到 obj 实例中,而不是返回更新后的对象。你可以在函数末尾返回 obj,但这是不必要的。 - Cybrus
3
String toName = fromName.replace("get", "set"); 如果我的字段名为 thisisfieldsetted 会发生什么? - HuyTTQ
@HuyTTQ 你可以使用 String toName = fromName.replaceFirst("get", "set");。由于 if 条件断言该方法以 "get" 开头,因此只有开头的 get 会被替换。 - lyh543
显示剩余4条评论

22

我正在使用Spring框架。在项目中,我遇到了相同的问题。
为了解决这个问题,我使用了类BeanUtils和上述方法。

public static void copyProperties(Object source, Object target)

这是一个例子,

public class Model1 {
    private String propertyA;
    private String propertyB;

    public Model1() {
        this.propertyA = "";
        this.propertyB = "";
    }

    public String getPropertyA() {
        return this.propertyA;
    }

    public void setPropertyA(String propertyA) {
        this.propertyA = propertyA;
    }

    public String getPropertyB() {
        return this.propertyB;
    }

    public void setPropertyB(String propertyB) {
        this.propertyB = propertyB;
    }
}

public class Model2 {
    private String propertyA;

    public Model2() {
        this.propertyA = "";
    }

    public String getPropertyA() {
        return this.propertyA;
    }

    public void setPropertyA(String propertyA) {
        this.propertyA = propertyA;
    }
}

public class JustATest {

    public void makeATest() {
        // Initalize one model per class.
        Model1 model1 = new Model1();
        model1.setPropertyA("1a");
        model1.setPropertyB("1b");

        Model2 model2 = new Model2();
        model2.setPropertyA("2a");

        // Merge properties using BeanUtils class.
        BeanUtils.copyProperties(model2, model1);

        // The output.
        System.out.println("Model1.propertyA:" + model1.getPropertyA(); //=> 2a
        System.out.println("Model1.propertyB:" + model1.getPropertyB(); //=> 1b
    }
}

2
BeanUtils.copyProperties 不会检查复制的属性是否为 null,并且会忽略它们。 - nabster

14
也许可以像这样:
class A {
    String a;
    List<..> b;
    int c;

    public void merge(A other) {
        this.a = other.a == null ? this.a : other.a;
        this.b.addAll(other.b);
        this.c = other.c == 0 ? this.c : other.c;
    }
}

A a1 = new A();
A a2 = new A();

a1.a = "a prop";
a2.c = 34;

a1.merge(a2);

A.merge可能会返回一个新的A对象,而不是修改当前对象。


这正是我要推荐的 - 你必须在某个地方清晰地编码规则 - 除非它是绝对的最后手段,否则你不想向你的代码添加像反射这样的"魔法" - 而这种情况绝不是那么困难。 - Bill K
3
由于this.b可能为null,它无法满足对b的“从obj2获取相同的ArrayList”请求,因此会出现NullPointerException。 - user85421

7

只需适应布尔同步和区分大小写(驼峰表示法)即可。

public boolean merge(Object obj){

    if(this.equals(obj)){
        return false;
    }

    if(!obj.getClass().isAssignableFrom(this.getClass())){
        return false;
    }

    Method[] methods = obj.getClass().getMethods();

    for(Method fromMethod: methods){
        if(fromMethod.getDeclaringClass().equals(obj.getClass())
                && (fromMethod.getName().matches("^get[A-Z].*$")||fromMethod.getName().matches("^is[A-Z].*$"))){

            String fromName = fromMethod.getName();
            String toName ;
            if(fromName.matches("^get[A-Z].*")){
                toName = fromName.replace("get", "set");
            }else{
                toName = fromName.replace("is", "set");
            }

            try {
                Method toMetod = obj.getClass().getMethod(toName, fromMethod.getReturnType());
                Object value = fromMethod.invoke(this, (Object[])null);
                if(value != null){
                    toMetod.invoke(obj, value);
                }
            } catch (Exception e) {
                e.printStackTrace();
            } 
        }
    }

    return true;
}

6
如果您为属性创建getter和setter,您可以使用Commons BeanUtils的copyProperties方法。

5

将此方法添加到您的POJO中,然后像myObject.merge(newObject)一样使用它。它使用泛型循环遍历您的POJO字段,因此您不需要提及任何字段名

/**
 * Fill current object fields with new object values, ignoring new NULLs. Old values are overwritten.
 *
 * @param newObject Same type object with new values.
 */
public void merge(Object newObject) {

  assert this.getClass().getName().equals(newObject.getClass().getName());

  for (Field field : this.getClass().getDeclaredFields()) {

    for (Field newField : newObject.getClass().getDeclaredFields()) {

      if (field.getName().equals(newField.getName())) {

        try {

          field.set(
              this,
              newField.get(newObject) == null
                  ? field.get(this)
                  : newField.get(newObject));

        } catch (IllegalAccessException ignore) {
          // Field update exception on final modifier and other cases.
        }
      }
    }
  }
}

如果您不想在添加/删除字段时经常维护该方法,那么这是最佳解决方案。我会更进一步,不忽略"IllegalAccessException",因为它可能会隐藏问题,而是针对您不想合并的特定字段或您想以不同方式处理的字段有备选情况。 - Bret Royster

3

有一个动态的解决方案可以合并需要反射和递归的任意两个对象。

public <T> T merge(T local, T remote, ArrayList<String> listOfClass)
        throws IllegalAccessException, InstantiationException {
    Class<?> clazz = local.getClass();
    Object merged = clazz.newInstance();
    for (Field field : clazz.getDeclaredFields()) {
        field.setAccessible(true);
        Object localValue = field.get(local);
        Object remoteValue = field.get(remote);
        if (localValue != null) {
            if (listOfClass.contains(localValue.getClass().getSimpleName())) {
                field.set(merged, this.merge(localValue, remoteValue, listOfClass));
            } else {
                field.set(merged, (remoteValue != null) ? remoteValue : localValue);
            }
        } else if (remoteValue != null) {
            field.set(merged, remoteValue);
        }
    }
    return (T) merged;
}

变量说明:

  • local: 将被合并其他对象的对象
  • remote: 将与本地对象合并的对象
  • listOfClass: 给定对象中自定义类的ArrayList

该函数返回一个合并后的对象,可以直接使用。

太棒了! :)


2
在您的特殊情况下,看起来您想要一个新对象,该对象获取两个实例中的实际值。这是一个可以实现此功能的实现。应将该方法添加到类A中,以便它可以访问字段。请保留HTML标签。
 public A specialMergeWith(A other) {
   A result = new A();

   result.a = (a == null ? other.a : a);
   result.b = (b == null ? other.b : b);
   result.c = (c == DEFAULT_VALUE ? other.c : c);

   return result;
 }

0
public static Object mergeObjects(Object source, Object target) throws Exception {
        Field[] allFields = source.getClass().getDeclaredFields();
        for (Field field : allFields) {
            if(Modifier.isStatic(field.getModifiers()) || Modifier.isFinal(field.getModifiers())){
                continue;
            }

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

        return target;
    }

使用Java反射,仅支持同一类。

0
   @SuppressWarnings("unchecked")
    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);
          if( value1.getClass().isPrimitive()){
             Object value = (value2 != null) ? value2 : value1;
             field.set(returnValue, value);
          }
          mergeObjects(value1, value2);  
       }
       return (T) returnValue;
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接