【问题标题】:How to merge two complex objects in java如何在java中合并两个复杂对象
【发布时间】:2014-09-12 00:10:15
【问题描述】:

我有两个 java 对象,我想将它们合并为一个对象。问题是这两个对象不包含简单的原始类型属性(字段)它们包含复杂类型属性(如其他类型的对象和其他类型的对象列表)。

对象1:通过设置一些属性(字段)和返回

对象 2:通过设置一些属性(字段)返回,或者它可能返回它拥有但对象 1 不返回的类型的新对象。

对象 1 和对象 2 属于同一类型。

结果对象 3 = obj1 属性 + 如果类型与 obj1 相同,则从 obj 2 更新属性 + 来自 obj2 的新更新对象

【问题讨论】:

标签: java merge


【解决方案1】:

使用 spring 提供的 org.springframework.beans.BeanUtils 类很容易做到。或者Apache Commons BeanUtils library,我认为 Springs 版本要么基于,要么与之相同。

public static <T> T combine2Objects(T a, T b) throws InstantiationException, IllegalAccessException {
    // would require a noargs constructor for the class, maybe you have a different way to create the result.
    T result = (T) a.getClass().newInstance();
    BeanUtils.copyProperties(a, result);
    BeanUtils.copyProperties(b, result);
    return result;
}

如果你不能或没有 noargs 构造函数,也许你只是传递结果

public static <T> T combine2Objects(T a, T b, T destination) {
    BeanUtils.copyProperties(a, destination);
    BeanUtils.copyProperties(b, destination);
    return destination;
}

如果你不想复制空属性,你可以使用这样的东西:

public static void nullAwareBeanCopy(Object dest, Object source) throws IllegalAccessException, InvocationTargetException {
    new BeanUtilsBean() {
        @Override
        public void copyProperty(Object dest, String name, Object value)
                throws IllegalAccessException, InvocationTargetException {
            if(value != null) {
                super.copyProperty(dest, name, value);
            }
        }
    }.copyProperties(dest, source);
}

嵌套对象解决方案

这是一个更强大的解决方案。它支持嵌套对象复制,1+层深的对象将不再通过引用复制,而是将嵌套对象克隆或单独复制它们的属性。

/**
 * Copies all properties from sources to destination, does not copy null values and any nested objects will attempted to be
 * either cloned or copied into the existing object. This is recursive. Should not cause any infinite recursion.
 * @param dest object to copy props into (will mutate)
 * @param sources
 * @param <T> dest
 * @return
 * @throws IllegalAccessException
 * @throws InvocationTargetException
 */
public static <T> T copyProperties(T dest, Object... sources) throws IllegalAccessException, InvocationTargetException {
    // to keep from any chance infinite recursion lets limit each object to 1 instance at a time in the stack
    final List<Object> lookingAt = new ArrayList<>();

    BeanUtilsBean recursiveBeanUtils = new BeanUtilsBean() {

        /**
         * Check if the class name is an internal one
         * @param name
         * @return
         */
        private boolean isInternal(String name) {
            return name.startsWith("java.") || name.startsWith("javax.")
                    || name.startsWith("com.sun.") || name.startsWith("javax.")
                    || name.startsWith("oracle.");
        }

        /**
         * Override to ensure that we dont end up in infinite recursion
         * @param dest
         * @param orig
         * @throws IllegalAccessException
         * @throws InvocationTargetException
         */
        @Override
        public void copyProperties(Object dest, Object orig) throws IllegalAccessException, InvocationTargetException {
            try {
                // if we have an object in our list, that means we hit some sort of recursion, stop here.
                if(lookingAt.stream().anyMatch(o->o == dest)) {
                    return; // recursion detected
                }
                lookingAt.add(dest);
                super.copyProperties(dest, orig);
            } finally {
                lookingAt.remove(dest);
            }
        }

        @Override
        public void copyProperty(Object dest, String name, Object value)
                throws IllegalAccessException, InvocationTargetException {
            // dont copy over null values
            if (value != null) {
                // attempt to check if the value is a pojo we can clone using nested calls
                if(!value.getClass().isPrimitive() && !value.getClass().isSynthetic() && !isInternal(value.getClass().getName())) {
                    try {
                        Object prop = super.getPropertyUtils().getProperty(dest, name);
                        // get current value, if its null then clone the value and set that to the value
                        if(prop == null) {
                            super.setProperty(dest, name, super.cloneBean(value));
                        } else {
                            // get the destination value and then recursively call
                            copyProperties(prop, value);
                        }
                    } catch (NoSuchMethodException e) {
                        return;
                    } catch (InstantiationException e) {
                        throw new RuntimeException("Nested property could not be cloned.", e);
                    }
                } else {
                    super.copyProperty(dest, name, value);
                }
            }
        }
    };


    for(Object source : sources) {
        recursiveBeanUtils.copyProperties(dest, source);
    }

    return dest;
}

它有点快速和肮脏,但效果很好。由于它确实使用了递归,并且存在无限递归的可能性,因此我确实将其置于安全位置。

【讨论】:

  • 您好,感谢您的回复,这里发生的事情是当我调用 BeanUtils.copyProperties(b,destination); BeanUtils.copyProperties(a, destination) 复制的属性;正在被覆盖并为某些属性获取空值
  • @user3860615 是的,之前遇到过这个问题,更新了 awnser。
  • @ug_ 我认为 nullAwareBeanCopy 不支持嵌套对象合并。
  • 您可以参考stackoverflow.com/questions/19737626中的答案以获得其他解决方案
【解决方案2】:

下面的方法将忽略serialVersionUID,遍历所有字段并从对象a->对象b复制非空值,如果它们在b中为空。换句话说,如果 b 中的任何字段为空,如果它不为空,则从 a 中获取它。

public static <T> T combine2Objects(T a, T b) throws InstantiationException,IllegalAccessException{
            T result = (T) a.getClass().newInstance();
            Object[] fields = Arrays.stream(a.getClass().getDeclaredFields()).filter(f -> !f.getName().equals("serialVersionUID")).collect(Collectors.toList()).toArray();
            for (Object fieldobj : fields) {
                Field field = (Field) fieldobj;
                field.set(result, field.get(b) != null ? field.get(b) : field.get(a));
            }
            return result;
    }

【讨论】:

    【解决方案3】:

    尝试使用class.getFields

        Field[] fields = YourClass.getFields();
        for (Field field : fields) {
             // get value
             YourObject value = field.get(objectInstance);
             // check the values are different, then update 
             field.set(objetInstance, value);    
        }
    

    【讨论】:

    • 感谢 tokhi,但它仅适用于一种复杂程度。
    • 但是你仍然可以在内部层使用同样的东西
    【解决方案4】:

    试试这个

    public <T> T objectMerge(T local, T remote, boolean toappend) throws Exception {
        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) {
                String key = "";
                if (localValue.getClass().getSimpleName().toLowerCase().contains("map")) {
                    key = "map";
                } else if (localValue.getClass().getSimpleName().toLowerCase().contains("set")) {
                    key = "set";
                } else if (localValue.getClass().getSimpleName().toLowerCase().contains("list")) {
                    key = "list";
                } else {
                    key = localValue.getClass().getSimpleName();
                }
                switch (key) {
                    case "Default":
                    case "Detail":
                    case "String":
                    case "Date":
                    case "Integer":
                    case "Float":
                    case "Long":
                    case "Double":
                    case "Object":
                        field.set(merged, (remoteValue != null) ? remoteValue : localValue);
                        break;
                    case "map":
                        if (toappend) {
                            ((Map) localValue).putAll((Map) remoteValue);
                        } else {
                            localValue = (remoteValue != null) ? remoteValue : localValue;
                        }
                        field.set(merged, localValue);
                        break;
                    case "list":
                        if (toappend) {
                            ((List) localValue).addAll((List) remoteValue);
                        } else {
                            localValue = (remoteValue != null) ? remoteValue : localValue;
                        }
                        field.set(merged, localValue);
                        break;
                    case "set":
                        if (toappend) {
                            ((Set) localValue).addAll((Set) remoteValue);
                        } else {
                            localValue = (remoteValue != null) ? remoteValue : localValue;
                        }
                        field.set(merged, localValue);
                        break;
                    default:
                        field.set(merged, this.objectMerge(localValue, remoteValue, toappend));
                        break;
                }
            }
        }
        return (T) merged;
    }
    

    【讨论】:

      【解决方案5】:
      import java.util.Objects;
      import java.util.function.Consumer;
      import java.util.function.Supplier;
      
      public final class PropertyMerger {
      
          public static <T> void mergeProperty(
                  Supplier<T> sourceGetter,
                  Supplier<T> targetGetter,
                  Consumer<T> targetSetter
          ) {
              var source = sourceGetter.get();
              var target = targetGetter.get();
      
              if (!Objects.equals(source, target)) {
                  targetSetter.accept(source);
              }
          }
      
      }
      

      代码中的某处:

      PropertyMerger.mergeProperty(facebookOAuth2User::getId, existingFacebookOAuth2UserDB::getFacebookId, existingFacebookOAuth2UserDB::setFacebookId);
      PropertyMerger.mergeProperty(facebookOAuth2User::getName, existingFacebookOAuth2UserDB::getName, existingFacebookOAuth2UserDB::setName);
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2019-08-27
        • 2019-10-08
        • 1970-01-01
        • 2017-10-19
        • 1970-01-01
        • 1970-01-01
        • 2011-03-19
        • 1970-01-01
        相关资源
        最近更新 更多