Comparator.comparing(...).thenComparing(...) 找出哪些字段不匹配

Comparator.comparing(...).thenComparing(...) find out which fields did not match

我正在尝试比较相同 class 的两个对象,目标是比较它们并确定哪些字段不匹配。

我的域名示例 class

@Builder(toBuilder=true)
class Employee {
     String name;
     int age;
     boolean fullTimeEmployee;
}

两个对象

Employee emp1 = Employee.builder().name("john").age(25).fullTime(false).build();
Employee emp2 = Employee.builder().name("Doe").age(25).fullTime(true).build();

比较两个对象

int result = Comparator.comparing(Employee::getName, Comparator.nullsFirst(Comparator.naturalOrder()))
                       .thenComparing(Employee::getAge, Comparator.nullsFirst(Comparator.naturalOrder()))
                       .thenComparing(Employee::isFullTimeEmployee, Comparator.nullsFirst(Comparator.naturalOrder()))
                       .compare(emp1, emp2);

结果将是 0,因为 namefullTime 字段彼此不匹配。

但我还想生成一个不匹配的字段列表.. 如下所示

List<String> unmatchedFields = ["name","fulltimeEmployee"];

除了一堆 if() else

之外,我可以用更好的方式来做吗?

首先,我认为您不了解 comparing().thenComparing() 的工作原理。你的情况的结果不会是 0。比较器比较第一个语句,如果它们相等,它会转到 thenComparing() 等等。当且仅当所有 comparison()/thenComparing() 也相等时,结果将为 0。我几天前写过这篇文章:http://petrepopescu.tech/2021/01/simple-collection-manipulation-in-java-using-lambdas/

无论如何,回到您的问题,我认为您正在寻找一个 equal() ,其中它还 returns 哪些字段不相等。这是一个快速原型。

public class Pair<T, U> {
    private Function<? super T, ? extends Comparable> function;
    private String fieldName;

    public Pair(Function<? super T, ? extends Comparable> function, String fieldName) {
        this.function = function;
        this.fieldName = fieldName;
    }
}

实际比较器:

public class MyComparator {
    List<Pair> whatToCompare = Arrays.asList(
            new Pair<Employee, String>(Employee::getName, "name"),
            new Pair<Employee, String>(Employee::getAge, "age"),
            new Pair<Employee, Double>(Employee::isFullTimeEmployee, "fullTimeEmployee")
    );
    public List<String> compare(Employee e1, Employee e2) {
        List<String> mismatch = new ArrayList<>();
        for(Pair pair:whatToCompare) {
            int result = Comparator.comparing(pair.getFunction()).compare(e1, e2);
            if (result != 0) {
                mismatch.add(pair.getFieldName());
            }
        }

        return mismatch;
    }
}

查看 DiffBuilder。它可以报告哪些项目不同。

DiffResult<Employee> diff = new DiffBuilder(emp1, emp2, ToStringStyle.SHORT_PREFIX_STYLE)
       .append("name", emp1.getName(), emp2.getName())
       .append("age", emp1.getAge(), emp2.getAge())
       .append("fulltime", emp1.getFulltime(), emp2.getFulltime())
       .build();

DiffResult 有一个 getDiffs() 方法,您可以循环查找不同之处。

Comparator本身没有发现差异的能力,它只比较两个相同类型的对象和returns -101.这是接口契约。

要发现实际差异,需要使用反射API获取字段,比较两个传递对象的真实值是否相等,并列出差异。这是一个通用的实现:

public class Difference<T> {

    T left;
    T right;

    //all-args constructor omitted

    public Set<String> differences() throws IllegalAccessException {

        // fieldName-field as a key-value  for the left and right instances
        Map<String, Field> leftFields = Arrays.stream(left.getClass().getDeclaredFields())
            .peek(f -> f.setAccessible(true))
            .collect(Collectors.toMap(Field::getName, Function.identity()));
        Map<String, Field> rightFields = Arrays.stream(left.getClass().getDeclaredFields())
            .peek(f -> f.setAccessible(true))
            .collect(Collectors.toMap(Field::getName, Function.identity()));

        // initialize Set as long as two fields cannot have a same name
        Set<String> differences = new HashSet<>();

        // list the fields of the left instance
        for (Entry<String, Field> entry: leftFields.entrySet()) {
            String fieldName = entry.getKey();
            Field leftField = entry.getValue();
            // find the right instance field matching the name
            Field rightField = rightFields.get(fieldName);
            // compare their actual values and add among the differences if they aren't equal
            if (!leftField.get(left).equals(rightField.get(right))) {
                differences.add(fieldName);
            }
        }
        return differences;
    }
}
Employee emp1 = Employee.builder().name("john").age(25).fullTime(false).build();
Employee emp2 = Employee.builder().name("Doe").age(25).fullTime(true).build();
        
Set<String> differences = new Difference<Employee>(emp1, emp2).differences();
        
System.out.println(differences);

[name, fullTimeEmployee]