Compare fields of two unrelated objects

2019-09-08 08:29发布

问题:

I've got two objects of different classes that share some fields with the same name and type. These two objects are not related to each other. There's no possibility for me to create an interface or a parent class.

Now I want to compare those shared fields and as far as I know this should be possible using reflection.

These are the steps I've written for such a compare method:

Field[] inputFields = input.getClass().getDeclaredFields();
for (Field field : inputFields ) {
    log.info(field.getName() + " : " + field.getType());
}

There will be an object called database to which's fields the inputFields are compared.

Unfortunately I don't know how to get the value of my fields. Do you have some hints for me?


Ok, with field.get(input) I've got the value now, but maybe I was wrong and that's not what I need. Actually, I want to compare this field with another one, so I need to call the equals method on this field. But at first I've got to cast it to it's appropriate class. So is there something like ((field.getClass()) field).equals(...) that would work?

回答1:

I think you're looking for Field.get():

for (Field field : inputFields ) {
    log.info(field.getName() + " : " 
             + field.getType() + " = " 
             + field.get(input);
}


回答2:

Check dedicated chapter of Sun's Java tutorial. This page answers your particular question with example.



回答3:

Here is a Solution to this problem, a utility class called FieldHelper that has a method

Map<String, Object[]> properties =
    FieldHelper.getCommonProperties(Object a, Object b)

The returned map has the field name as key and an array of the two field values as value:

public final class FieldHelper{

    private FieldHelper(){}

    private static final Map<Class<?>, Map<String, PropertyDescriptor>> cache =
        new HashMap<Class<?>, Map<String, PropertyDescriptor>>();

    /**
     * Return a Map of field names to {@link PropertyDescriptor} objects for a
     * given bean.
     */
    public static Map<String, PropertyDescriptor> getBeanProperties(final Object o){

        try{
            final Class<?> clazz = o.getClass();
            Map<String, PropertyDescriptor> descriptors;
            if(cache.containsKey(clazz)){
                descriptors = cache.get(clazz);
            } else{
                final BeanInfo beanInfo =
                    Introspector.getBeanInfo(clazz, Object.class);
                descriptors = new TreeMap<String, PropertyDescriptor>();
                for(final PropertyDescriptor pd : beanInfo.getPropertyDescriptors()){
                    descriptors.put(pd.getName(), pd);
                }
                cache.put(clazz,
                    new TreeMap<String, PropertyDescriptor>(descriptors));
            }
            final Map<String, PropertyDescriptor> beanProperties = descriptors;
            return beanProperties;

        } catch(final IntrospectionException e){
            throw new IllegalStateException("Can't get bean metadata", e);
        }
    }

    /**
     * Return a Map of all field names and their respective values that two
     * objects have in common. Warning: the field values can be of different
     * types.
     */
    public static Map<String, Object[]> getCommonProperties(final Object a,
        final Object b){
        final Map<String, PropertyDescriptor> aProps = getBeanProperties(a);
        final Map<String, PropertyDescriptor> bProps = getBeanProperties(b);
        final Set<String> aKeys = aProps.keySet();
        final Set<String> bKeys = bProps.keySet();
        aKeys.retainAll(bKeys);
        bKeys.retainAll(aKeys);
        final Map<String, Object[]> map = new TreeMap<String, Object[]>();

        for(final String propertyName : aKeys){
            final Object aVal = getPropertyValue(a, aProps.get(propertyName));
            final Object bVal = getPropertyValue(b, bProps.get(propertyName));
            map.put(propertyName, new Object[] { aVal, bVal });
        }
        return map;
    }

    /**
     * Return the value of a bean property, given the bean and the {@link PropertyDescriptor}.
     */
    private static Object getPropertyValue(final Object a,
        final PropertyDescriptor propertyDescriptor){
        try{
            return propertyDescriptor.getReadMethod().invoke(a);
        } catch(final IllegalArgumentException e){
            throw new IllegalStateException("Bad method arguments", e);
        } catch(final IllegalAccessException e){
            throw new IllegalStateException("Can't access method", e);
        } catch(final InvocationTargetException e){
            throw new IllegalStateException("Invocation error", e);
        }
    }

Test Code:

public static void main(final String[] args){
    class Foo{
        private String abc = "abc";
        private String defy = "defy";
        private String ghi = "ghi";
        private String jkl = "jkl";
        // stripped getters and setters
        // they must be there for this to work
    }
    class Bar{
        private Boolean abc = true;
        private Integer def = 3;
        private String ghix = "ghix3";
        private Date jkl = new Date();
        // stripped getters and setters
        // they must be there for this to work
    }
    final Map<String, Object[]> properties =
        getCommonProperties(new Foo(), new Bar());
    for(final Entry<String, Object[]> entry : properties.entrySet()){
        System.out.println("Field: " + entry.getKey() + ", value a: "
            + entry.getValue()[0] + ", value b: " + entry.getValue()[1]);
    }
}

Output:

Field: abc, value a: abc, value b: true
Field: jkl, value a: jkl, value b: Tue Oct 12 14:03:31 CEST 2010

Note: this code doesn't actually read the fields, it follows the java bean convention and uses the getters instead. It would be easy to rewrite it to use fields, but I would advise against it.