简体   繁体   中英

How to merge two java objects into one?

I have two java object instances and I want to merge their values into one instance. I need to pick which object instance takes precedence when both object instances contain a value for a field. I also do not want to override values with null values.

Example:

MyClass source = new MyClass("source", 1, null);
MyClass target = new MyClass("target", 2, "b");
merge(source, target);
// target now has values ("source", 1, "b")

I'm using Java 8 and Spring boot 1.4.1.

Edit: It looks like I was not clear so I added more description. In addition, I already provided my own solution. The intent was to contribute this solution for when others have the same issue. I've found other threads on here asking the same or similar question, but they did not have a complete solution like I posted below.

Spring's spring-beans library has a org.springframework.beans.BeanUtils class that provides a copyProperties method to copy a source object instance into a target object instance. However, it only does so for the object's first level fields. Here is my solution, based on BeanUtils.copyProperties , to recursively perform the copy for every child object including collections and maps.

package my.utility;

import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeansException;
import org.springframework.beans.FatalBeanException;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;

/**
 * Created by cdebergh on 1/6/17.
 */
public class BeanUtils extends org.springframework.beans.BeanUtils {

    /**
     * Copy the not null property values of the given source bean into the target bean.
     * <p>Note: The source and target classes do not have to match or even be derived
     * from each other, as long as the properties match. Any bean properties that the
     * source bean exposes but the target bean does not will silently be ignored.
     * <p>This is just a convenience method. For more complex transfer needs,
     * consider using a full BeanWrapper.
     * @param source the source bean
     * @param target the target bean
     * @throws BeansException if the copying failed
     * @see BeanWrapper
     */
    public static void copyPropertiesNotNull(Object source, Object target) throws BeansException {
        copyPropertiesNotNull(source, target, null, (String[]) null);
    }

    private static void setAccessible(Method method) {
        if (!Modifier.isPublic(method.getDeclaringClass().getModifiers())) {
            method.setAccessible(true);
        }
    }

    /**
     * Copy the not null property values of the given source bean into the given target bean.
     * <p>Note: The source and target classes do not have to match or even be derived
     * from each other, as long as the properties match. Any bean properties that the
     * source bean exposes but the target bean does not will silently be ignored.
     * @param source the source bean
     * @param target the target bean
     * @param editable the class (or interface) to restrict property setting to
     * @param ignoreProperties array of property names to ignore
     * @throws BeansException if the copying failed
     * @see BeanWrapper
     */
    private static void copyPropertiesNotNull(Object source, Object target, Class<?> editable, String... ignoreProperties)
            throws BeansException {

        Assert.notNull(source, "Source must not be null");
        Assert.notNull(target, "Target must not be null");

        Class<?> actualEditable = target.getClass();
        if (editable != null) {
            if (!editable.isInstance(target)) {
                throw new IllegalArgumentException("Target class [" + target.getClass().getName() +
                        "] not assignable to Editable class [" + editable.getName() + "]");
            }
            actualEditable = editable;
        }
        PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);
        List<String> ignoreList = (ignoreProperties != null ? Arrays.asList(ignoreProperties) : null);

        for (PropertyDescriptor targetPropertyDescriptor : targetPds) {
            Method targetWriteMethod = targetPropertyDescriptor.getWriteMethod();
            if (targetWriteMethod != null
                    && (ignoreList == null || !ignoreList.contains(targetPropertyDescriptor.getName()))) {
                PropertyDescriptor sourcePropertyDescriptor =
                        getPropertyDescriptor(source.getClass(), targetPropertyDescriptor.getName());
                if (sourcePropertyDescriptor != null) {
                    Method sourceReadMethod = sourcePropertyDescriptor.getReadMethod();
                    if (sourceReadMethod != null &&
                            ClassUtils.isAssignable(
                                    targetWriteMethod.getParameterTypes()[0], sourceReadMethod.getReturnType())) {
                        try {
                            Method targetReadMethod = targetPropertyDescriptor.getReadMethod();
                            setAccessible(sourceReadMethod);
                            setAccessible(targetWriteMethod);
                            Object sourceValue = sourceReadMethod.invoke(source);

                            if (sourceValue != null && targetReadMethod != null) {
                                setAccessible(targetReadMethod);
                                Object targetValue = targetReadMethod.invoke(target);
                                if (targetValue == null) {
                                    targetWriteMethod.invoke(target, sourceValue);
                                } else if(targetValue instanceof Collection<?>) {
                                    ((Collection) targetValue).addAll((Collection) sourceValue);
                                } else if (targetValue instanceof Map<?,?>) {
                                    ((Map) targetValue).putAll((Map) sourceValue);
                                } else {
                                    copyPropertiesNotNull(sourceValue, targetValue, editable, ignoreProperties);
                                }
                            }
                        }
                        catch (Throwable ex) {
                            throw new FatalBeanException(
                                    "Could not copy property '" + targetPropertyDescriptor.getName() +
                                    "' from source to target", ex);
                        }
                    }
                }
            }
        }
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM