如何在java中合并两个复杂对象
Posted
技术标签:
【中文标题】如何在java中合并两个复杂对象【英文标题】:How to merge two complex objects in java 【发布时间】:2014-09-12 00:10:15 【问题描述】:我有两个 java 对象,我想将它们合并为一个对象。问题是这两个对象不包含简单的原始类型属性(字段)它们包含复杂类型属性(如其他类型的对象和其他类型的对象列表)。
对象1:通过设置一些属性(字段)和返回
对象 2:通过设置一些属性(字段)返回,或者它可能返回它拥有但对象 1 不返回的类型的新对象。
对象 1 和对象 2 属于同一类型。
结果对象 3 = obj1 属性 + 如果类型与 obj1 相同,则从 obj 2 更新属性 + 来自 obj2 的新更新对象
【问题讨论】:
展示你的努力。到目前为止,您尝试了什么? 看看这个类,特别是这个方法docs.spring.io/spring/docs/2.5.6/api/org/springframework/beans/…,java.lang.Object) 【参考方案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 不支持嵌套对象合并。 您可以参考***.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);
【讨论】:
以上是关于如何在java中合并两个复杂对象的主要内容,如果未能解决你的问题,请参考以下文章