Using the Apache Commons Lang 3 for Comparing Objects in Java – 使用 Apache Commons Lang 3 比较 Java 中的对象

最后修改: 2023年 10月 18日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

Comparing objects is one of the core concepts in Java, as well as in many other programming languages. It’s an essential concept when dealing with sorting, searching, and filtering data, which plays a crucial role in various aspects of programming.

比较对象是 Java 以及许多其他编程语言的核心概念之一。它是处理排序、搜索和过滤数据时的一个基本概念,在编程的各个方面都起着至关重要的作用。

Comparing objects in Java can be done manually by implementing the comparison logic or using libraries with object comparison abilities. Various libraries can be used for comparing objects in Java, such as JaVers or Apache Commons Lang 3, which we will cover in this article.

在 Java 中比较对象可以通过手动实现比较逻辑或使用具有对象比较能力的库来完成。可以使用各种库来比较 Java 中的对象,例如 JaVers 或 Apache Commons Lang 3,我们将在本文中介绍这些库。

2. About Apache Commons Lang 3

2.关于 Apache Commons Lang 3

Apache Commons Lang 3 represents the 3.0 version of the Apache Commons Lang library, which offers many functionalities.

Apache Commons Lang 3 是 Apache Commons Lang 库的 3.0 版本,提供多种功能

We will explore the DiffBuilder class to compare and obtain the differences between two objects of the same type. The resulting differences are represented with the DiffResult class.

我们将探索 DiffBuilder 类,以比较和获取两个相同类型对象之间的差异。DiffResult 类表示由此产生的差异。

There is also an alternative to the DiffBuilderReflectionDiffBuilder – which serves the same purpose, but it’s based on reflection, while DiffBuilder is not.

DiffBuilder 还有一个替代方案 – ReflectionDiffBuilder – 它具有相同的功能,但它基于反射,而 DiffBuilder 不是。

3. Maven Dependencies

3. Maven 依赖项 4.

To use Apache Commons Lang 3, we first need to add the Maven dependency:

要使用 Apache Commons Lang 3,我们首先需要添加 Maven 依赖项

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.14.0</version>
</dependency>

4. Model

4.模型

To demonstrate comparing two objects and obtaining their differences, we will use a Person class, along with PhoneNumber and Address classes:

为了演示比较两个对象并获取它们之间的差异,我们将使用 Person 类以及 PhoneNumberAddress 类:

public class Person {
    private String firstName;
    private String lastName;
    private int age;
    private List<PhoneNumber> phoneNumbers;
    private Address address;

    // standard constructors, getters and setters
}
public class PhoneNumber {
    private String type;
    private String number;

    // standard constructors, getters and setters
}
public class Address {
    private String streetAddress;
    private String city;
    private String postalCode;

    // standard constructors, getters and setters
}

5. Comparing Objects Using the DiffBuilder Class

5.使用 DiffBuilder 类比较对象

Let’s demonstrate how to compare Person objects by using the DiffBuilder class. We’ll first start by defining a PersonDiffBuilder class with a compare() method:

让我们演示如何使用 DiffBuilder 类来比较 Person 对象。首先,我们将定义一个带有 compare() 方法的 PersonDiffBuilder 类:

public static DiffResult compare(Person first, Person second) {
    DiffBuilder diffBuilder = new DiffBuilder(first, second, ToStringStyle.DEFAULT_STYLE)
      .append("person", first.getFirstName(), second.getFirstName())
      .append("lastName", first.getLastName(), second.getLastName())
      .append("streetAddress", first.getAddress().getStreetAddress(), 
        second.getAddress().getStreetAddress())
      .append("city", first.getAddress().getCity(), second.getAddress().getCity())
      .append("postalCode", first.getAddress().getPostalCode(), 
        second.getAddress().getPostalCode())
      .append("age", first.getAge(), second.getAge());

    for (int i = 0; i < first.getPhoneNumbers().size(); i++) {
        diffBuilder.append("phoneNumbers[" + i + "].number", 
          first.getPhoneNumbers().get(i).getNumber(), 
          second.getPhoneNumbers().get(i).getNumber());
    }
    return diffBuilder.build();
}

Here, we use the DiffBuilder to implement the compare() method. When generating the DiffBuilder using the append() methods, we can control exactly which fields will be used in the comparison.

在此,我们使用 DiffBuilder 来实现 compare() 方法。使用 append() 方法生成 DiffBuilder 时,我们可以精确控制比较中将使用哪些字段。

For demonstration purposes, when comparing the nested PhoneNumber objects, we omit comparing the type field, so two PhoneNumber objects with the same number and a different type field will be considered equal.

为便于演示,在比较嵌套的 PhoneNumber 对象时,我们省略了对 type 字段的比较,因此具有相同 number 和不同 type 字段的两个 PhoneNumber 对象将被视为相同。

Optionally, we could make the Person class implement the Diffable interface and then use the DiffBuilder similarly to implement the diff() method.

作为选择,我们可以让 Person 类实现 Diffable 接口,然后类似地使用 DiffBuilder 来实现 diff() 方法。

Let’s see how we can put the PersonDiffBuilder class into practice and compare two Person objects:

让我们看看如何将 PersonDiffBuilder 类付诸实践,并比较两个 Person 对象:

@Test
void givenTwoPeopleDifferent_whenComparingWithDiffBuilder_thenDifferencesFound() {
    List<PhoneNumber> phoneNumbers1 = new ArrayList<>();
    phoneNumbers1.add(new PhoneNumber("home", "123-456-7890"));
    phoneNumbers1.add(new PhoneNumber("work", "987-654-3210"));

    List<PhoneNumber> phoneNumbers2 = new ArrayList<>();
    phoneNumbers2.add(new PhoneNumber("mobile1", "123-456-7890"));
    phoneNumbers2.add(new PhoneNumber("mobile2", "987-654-3210"));

    Address address1 = new Address("123 Main St", "London", "12345");
    Address address2 = new Address("123 Main St", "Paris", "54321");

    Person person1 = new Person("John", "Doe", 30, phoneNumbers1, address1);
    Person person2 = new Person("Jane", "Smith", 28, phoneNumbers2, address2);

    DiffResult<Person> diff = PersonDiffBuilder.compare(person1, person2);
    for (Diff<?> d : diff.getDiffs()) {
        System.out.println(d.getFieldName() + ": " + d.getLeft() + " != " + d.getRight());
    }

    assertFalse(diff.getDiffs().isEmpty());
}

The resulting DiffResult provides a getDiffs() method for obtaining the differences found as a list of Diff objects. Diff class also offers practical methods for obtaining the compared fields.

生成的 DiffResult 提供了一个 getDiffs() 方法,用于以 Diff 对象列表的形式获取发现的差异。 Diff 类还提供了用于获取比较字段的实用方法。

In this example, persons under comparison had a different first name, last name, city, and postal code. The phone numbers have different types but equal numbers.

在这个例子中,被比较的人有不同的名、姓、城市和邮政编码。电话号码的类型不同,但号码相同。

If we take a look at the System.out.println() output, we can see that all the differences have been found:

如果我们查看一下 System.out.println() 输出,就会发现所有差异都已找到:

person: John != Jane
lastName: Doe != Smith
city: London != Paris
postalCode: 12345 != 54321
age: 30 != 28

6. Comparing Objects Using the ReflectionDiffBuilder Class

6.使用 ReflectionDiffBuilder 类比较对象

Let’s demonstrate how to compare Person objects by using the ReflectionDiffBuilder class. We’ll first start by defining a PersonReflectionDiffBuilder class with a compare() method:

让我们演示如何使用 ReflectionDiffBuilder 类来比较 Person 对象。首先,我们将定义一个带有 compare() 方法的 PersonReflectionDiffBuilder 类:

public static DiffResult compare(Person first, Person second) {
    return new ReflectionDiffBuilder<>(first, second, ToStringStyle.SHORT_PREFIX_STYLE).build();
}

Here, we use the ReflectionDiffBuilder to implement the compare() method. There is no need to append individual fields for comparison, as all the non-static and non-transient fields are discovered using reflection.

在这里,我们使用 ReflectionDiffBuilder 来实现 compare() 方法。无需附加单个字段进行比较,因为所有非静态和非瞬时字段都是通过反射发现的。

In this example, the discovered fields would be firstName, lastName, age, phoneNumbers, and address. Internally, the ReflectionDiffBuilder uses the DiffBuilder, and it’s built using the discovered fields.

在这个示例中,发现的字段是 年龄电话号码地址。在内部,ReflectionDiffBuilder使用DiffBuilder,并使用发现的字段构建。

Suppose we want to exclude a specific discovered field from the comparison. In that case, we can use the @DiffExclude annotation to mark the fields we wish to exclude from the ReflectionDiffBuilder‘s use.

假设我们想从比较中排除特定的已发现字段。在这种情况下,我们可以使用 @DiffExclude 注解来标记我们希望从 ReflectionDiffBuilder 的使用中排除的字段。

Since our Person class has a complex structure with nested objects, to ensure that ReflectionDiffBuilder correctly identifies the differences, we have to implement equals() and hashCode() methods.

由于我们的Person类具有嵌套对象的复杂结构,为确保ReflectionDiffBuilder正确识别差异,我们必须实现equals()hashCode()方法。

For demonstration purposes, we will mark the address field from the Person class with the @DiffExclude annotation:

为便于演示,我们将使用 @DiffExclude 注解标记 Person 类中的 address 字段:

public class Person {
    private String firstName;
    private String lastName;
    private int age;
    private List<PhoneNumber> phoneNumbers;
    @DiffExclude
    private Address address;

    // standard constructors, getters and setters
}

We’ll also omit using the type field in the equals() and hashCode() methods for the PhoneNumber class:

我们还将省略在 PhoneNumber 类的 equals() hashCode() 方法中使用 type 字段:

@Override
public boolean equals(Object o) {
    if (this == o) {
        return true;
    }
    if (o == null || getClass() != o.getClass()) {
        return false;
    }
    PhoneNumber that = (PhoneNumber) o;
    return Objects.equals(number, that.number);
}

@Override
public int hashCode() {
    return Objects.hash(number);
}

Let’s see how we can use the PersonReflectionDiffBuilder class for comparing two Person objects:

让我们看看如何使用 PersonReflectionDiffBuilder 类来比较两个 Person 对象:

@Test
void givenTwoPeopleDifferent_whenComparingWithReflectionDiffBuilder_thenDifferencesFound() {
    List<PhoneNumber> phoneNumbers1 = new ArrayList<>();
    phoneNumbers1.add(new PhoneNumber("home", "123-456-7890"));
    phoneNumbers1.add(new PhoneNumber("work", "987-654-3210"));

    List<PhoneNumber> phoneNumbers2 = new ArrayList<>();
    phoneNumbers2.add(new PhoneNumber("mobile1", "123-456-7890"));
    phoneNumbers2.add(new PhoneNumber("mobile2", "987-654-3210"));

    Address address1 = new Address("123 Main St", "London", "12345");
    Address address2 = new Address("123 Main St", "Paris", "54321");

    Person person1 = new Person("John", "Doe", 30, phoneNumbers1, address1);
    Person person2 = new Person("Jane", "Smith", 28, phoneNumbers2, address2);

    DiffResult<Person> diff = PersonReflectionDiffBuilder.compare(person1, person2);
    for (Diff<?> d : diff.getDiffs()) {
        System.out.println(d.getFieldName() + ": " + d.getLeft() + " != " + d.getRight());
    }

    assertFalse(diff.getDiffs().isEmpty());
}

In this example, persons under comparison had different first names, last names, and addresses. The phone numbers have different types but equal numbers. However, we put the @DiffExclude annotation on the address field to exclude it from the comparison.

在这个例子中,被比较的人有不同的名字、姓氏和地址。电话号码的类型不同,但号码相同。但是,我们在 address 字段上添加了 @DiffExclude 注解,以将其排除在比较范围之外。

If we take a look at the System.out.println() output, we can see that all the differences have been found:

如果我们查看一下 System.out.println() 输出,就会发现所有差异都已找到:

firstName: John != Jane
lastName: Doe != Smith
age: 30 != 28

7. Conclusion

7.结论

In this article, we demonstrated how to compare Java objects using the DiffBuilder and the ReflectionDiffBuilder from the Apache Commons Lang 3 library.

在本文中,我们演示了如何使用 Apache Commons Lang 3 库中的 DiffBuilderReflectionDiffBuilder 比较 Java 对象。

Both classes are easy to use and offer a convenient way to compare objects, although each has advantages and disadvantages.

这两个类都易于使用,并提供了比较对象的便捷方法,但各有利弊。

We have seen through the examples in this article that DiffBuilder offers more customization and is more explicit. Still, it might result in increased complexity for more complex objects.

我们通过本文中的示例看到,DiffBuilder 提供了更多的自定义功能,而且更加明确。不过,对于更复杂的对象,它可能会导致复杂性增加。

ReflectionDiffBuilder offers more simplicity but has limited customization options and might introduce performance overhead since it uses reflection.

ReflectionDiffBuilder提供了更多的简便性,但自定义选项有限,而且由于使用反射,可能会带来性能开销。

Code from this article can be found over on GitHub.

本文的代码可在 GitHub 上找到