如何比较两个对象并找到更改的字段/属性?

时间:2018-01-18 13:00:24

标签: java

我需要编写一些通用解决方案来找出两个对象中的哪些属性已更改并返回已更改的属性(而不是值)。

class Student {
    public String name;
    public Address address;
    public int age;
}

class Address {
    public String hno;
    public String street;
    public int pin;
}

public class Test {
    public static void main(String... arg) {

        Student s1 = new Student();
        s1.name = "Krishna";
        s1.age = 30;
        s1.address = new Address();
        s1.address.hno = "2-2-22";
        s1.address.street = "somewhere";
        s1.address.pin = 123;

        Student s2 = new Student();
        s2.name = "Krishna";
        s2.age = 20;
        s2.address = new Address();
        s2.address.hno = "1-1-11";
        s2.address.street = "nowhere";
        s2.address.pin = 123;

        List<String> = difference(s1, s2);
        // expected result
        // Student.age
        // Student.address.hno
        // Student.address.street
    }
}

有人可以建议一些解决方案吗?

PS

覆盖equals / hashcode不是我的选择。

我编写了以下代码,但我不确定如何识别类型是否复杂(例如地址)

private static List<String> difference(Student s1, Student s2) throws IllegalArgumentException, IllegalAccessException {

        for(Field field: Student.class.getDeclaredFields()) {
            System.out.println(field.getName() + " " +field.get(s1).equals(field.get(s2)));
        }
        return null;
    }

5 个答案:

答案 0 :(得分:5)

这是Dumbo的difference方法的增强,它使用递归检查所有嵌套的复杂字段。

    private static void difference(Object s1, Object s2, List<String> changedProperties, String parent) throws IllegalAccessException {
        for (Field field : s1.getClass().getDeclaredFields()) {
            if (parent == null) {
                parent = s1.getClass().getSimpleName();
            }
            field.setAccessible(true);
            Object value1 = field.get(s1);
            Object value2 = field.get(s2);
            if (value1 == null && value2 == null) {
                continue;
            }
            if (value1 == null || value2 == null) {
                changedProperties.add(parent + "." + field.getName());
            } else {
                if (isBaseType(value1.getClass())) {
                    if (!Objects.equals(value1, value2)) {
                        changedProperties.add(parent + "." + field.getName());
                    }
                } else {
                    difference(value1, value2, changedProperties, parent + "." + field.getName());
                }
            }
        }
    }

    private static final Set<Class> BASE_TYPES = new HashSet(Arrays.asList(
            String.class, Boolean.class, Character.class, Byte.class, Short.class, Integer.class, Long.class, Float.class, Double.class, Void.class));
    public static boolean isBaseType(Class clazz) {
        return BASE_TYPES.contains(clazz);
    }

示例用法(假设引用的模型对象具有getter / setter方法):

    public static void main(String[] args) throws IllegalAccessException {
        Student s1 = new Student();
        s1.setName("Krishna");
        s1.setAge(30);
        Address address = new Address();
        s1.setAddress(address);
        s1.getAddress().setHno("2-2-22");
        s1.getAddress().setStreet("somewhere");
        s1.getAddress().setPin(123);

        Student s2 = new Student();
        s2.setName("Krishna");
        s2.setAge(20);
        address = new Address();
        s2.setAddress(address);
        s2.getAddress().setHno("1-1-11");
        s2.getAddress().setStreet("nowhere");
        s2.getAddress().setPin(123);
        List<String> changedProperties = new ArrayList<>();

        difference(s1, s2, changedProperties, null);
        System.out.println("changedProperties = " + changedProperties);
        // expected result
        // Student.age
        // Student.address.hno
        // Student.address.street
    }

结果:

changedProperties = [Student.address.hno, Student.address.street, Student.age]

https://stackoverflow.com/a/711226/1527469改编的原始适应性检查

答案 1 :(得分:1)

尽管这是一个古老的问题,但我想补充一下:-

  1. 使用JAXB将Java对象转换为XML。
  2. 使用XMLUnit比较XML中的差异。

它将为您提供已更改的字段的名称,这些字段的前后值等。

我承认这不是最好的方法,因为它具有编组和运行XML比较的开销。并且需要添加几个库。但这可能比通过字段比较(特别是深度比较)和对复杂模型对象的反射来编写字段更好。

答案 2 :(得分:0)

我认为这是您搜索的方法:

 private static List<String> difference(Student s1, Student s2) {
     List<String> values = new ArrayList<>();
     for (Field field : s1.getClass().getDeclaredFields()) {
        // You might want to set modifier to public first (if it is not public yet)
        field.setAccessible(true);
        Object value1 = field.get(s1);
        Object value2 = field.get(s2); 
        if (value != null && value != null) {
            System.out.println(field.getName() + "=" + value1);
            System.out.println(field.getName() + "=" + value2);
            if (!Objects.equals(value1, value2) {
                values.add(value2);
            }
        }
    }
    return values;
 }

答案 3 :(得分:0)

我想补充 Awgtek 的 方法很好,但不适用于 Enum 字段 我在 isBaseType(value1.getClass())

之前添加了此代码
if (value1 instanceof Enum && value2 instanceof Enum ) {
    if (!value1.equals(value2) {
        changedProperties.add(parent + "." + field.getName());
        continue;
    }
}

我还建议在 LocalDate.class 中添加 BASE_TYPES

如果您还想比较超类中的字段。 您需要更改 s1.getClass().getDeclaredFields() -> getAllFields(s1.getClass())

 private static List<Field> getAllFields(Class currentClass) {
    final List<Field> fields = new ArrayList<>(Arrays.asList(currentClass.getDeclaredFields()));

    final Class superclass = currentClass.getSuperclass();
    if (superclass != Object.class) {
        fields.addAll(getAllFields(superclass));
    }

    return fields;
}

答案 4 :(得分:-1)

1。符合您要求的解决方案:

//Add this in Student Class
public static List<String> difference(Student s1, Student s2) {
    List<String> res = new ArrayList<>();
    if (!s1.name.equalsIgnoreCase(s2.name)) res.add("name");
    if (s1.age != s2.age) res.add("age");
    res.addAll(Address.difference(s1.address, s2.address));
    return res;
}

//Add this in Address Class
public static List<String> difference(Address s1, Address s2) {
    List<String> res = new ArrayList<>();
    if (!s1.hno.equalsIgnoreCase(s2.hno)) res.add("adress.hno");
    if (!s1.street.equalsIgnoreCase(s2.street)) res.add("adress.street");
    if (s1.pin != s2.pin) res.add("pin");
    return res;
}

这将打印:[age, adress.hno, adress.street] 正确 但不容易获得更多属性

List<String> list = Student.difference(s1, s2);
System.out.println(list);

2。使用反射的版本会导致:

//Add this method in Test Class
private static List<String> difference(Student s1, Student s2) throws IllegalAccessException {
    List<String> res = new ArrayList<>();
    for (Field f : s1.getClass().getFields())
        if (!f.get(s1).equals(f.get(s2)))
            res.add(f.getName());
    return res;
}

这将打印:[address, age] 并非所有正确的 但如果更多的归因

则不会有任何变化
List<String> list = difference(s1, s2);
System.out.println(list);

3。提示:

  • 使用所有constructor创建attributs,不要在
  • 之后设置它们
  • 不要设置attributs public&amp;使用getterssetters
  • 中覆盖equals方法
class Student {
    private String name;    private Address address;      private int age;      
    public Student (String n, Address ad, int a){name = n; address = ad; age=a;}
}

class Address {    
    private String hno;     private String street;        private int pin;
    public Address (String a, String s, int p){hno = a; street = s; pin=p;}    
}

//To use in your main of Test (or elsewhere) like this    
Student s1 = new Student("Krishna", new Address("2-2-22", "somewhere", 123), 30);
Student s2 = new Student("Krishna", new Address("1-1-11", "nowhere", 123), 20);