1. 概述
在Java和其他许多编程语言中,比较对象是核心概念之一。在处理排序、搜索和过滤数据时,它扮演着至关重要的角色,对编程的各个方面都有着不可或缺的作用。
在Java中比较对象可以通过手动实现比较逻辑或使用具有对象比较能力的库来完成。Java中有多种库可用于对象比较,如JaVers或Apache Commons Lang 3,本文将对此进行介绍。
2. 关于Apache Commons Lang 3
Apache Commons Lang 3是Apache Commons Lang库的3.0版本,提供了众多功能。
我们将探索org.apache.commons.lang3.builder.DiffBuilder
类,用于比较同一类型的两个对象并获取它们之间的差异。差异结果由org.apache.commons.lang3.builder.DiffResult
类表示。
此外,ReflectionDiffBuilder
(链接)是DiffBuilder
的另一种选择,它们都服务于相同的目的,但ReflectionDiffBuilder
基于反射,而DiffBuilder
则不然。
3. Maven依赖
要使用Apache Commons Lang 3,首先需要添加Maven依赖:
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.14.0</version>
</dependency>
4. 模型
为了演示比较两个对象及其差异,我们将使用一个Person
类,以及PhoneNumber
和Address
类:
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. 使用DiffBuilder
类比较对象
现在我们通过DiffBuilder
类展示如何比较Person
对象。首先,我们定义一个名为PersonDiffBuilder
的类,并实现compare()
方法:
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();
}
在这里,我们使用DiffBuilder
来实现compare()
方法。当我们使用append()
方法生成DiffBuilder
时,可以精确控制哪些字段将参与比较。
在演示时,当我们比较嵌套的PhoneNumber
对象时,我们会忽略type
字段,因此拥有相同号码但类型不同的两个PhoneNumber
对象将被视为相等。
如果愿意,可以让Person
类实现Diffable
接口,然后同样使用DiffBuilder
来实现diff()
方法。
让我们看一个实际应用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());
}
得到的DiffResult
提供了getDiffs()
方法,用于获取发现的差异作为Diff
对象的列表。Diff
类还提供了获取比较字段的实际方法。
在这个例子中,比较的人有不同的人名、姓氏、城市和邮政编码。电话号码类型不同但号码相同。
查看System.out.println()
输出,我们可以看到所有差异已被找到:
person: John != Jane
lastName: Doe != Smith
city: London != Paris
postalCode: 12345 != 54321
age: 30 != 28
6. 使用ReflectionDiffBuilder
类比较对象
接下来,我们演示如何使用ReflectionDiffBuilder
类比较Person
对象。首先,我们定义一个名为PersonReflectionDiffBuilder
的类,并实现compare()
方法:
public static DiffResult compare(Person first, Person second) {
return new ReflectionDiffBuilder<>(first, second, ToStringStyle.SHORT_PREFIX_STYLE).build();
}
这里,我们使用ReflectionDiffBuilder
来实现compare()
方法。不需要为比较单独附加字段,因为所有非静态和非transient字段都会通过反射发现。
在本例中,发现的字段将是firstName
、lastName
、age
、phoneNumbers
和address
。内部地,ReflectionDiffBuilder
使用了DiffBuilder
,并基于发现的字段构建。
如果我们想排除比较中的特定发现字段,可以在ReflectionDiffBuilder
的使用上标记@DiffExclude
注解,标记我们希望排除的字段。
由于我们的Person
类有复杂的结构,包含嵌套的对象,为了确保ReflectionDiffBuilder
正确识别差异,我们需要实现equals()
和hashCode()
方法。
为了演示目的,我们将Person
类的address
字段标记为@DiffExclude
注解:
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
}
同时,我们将在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);
}
让我们看如何使用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());
}
在这个例子中,比较的人有不同的名字、姓氏和地址。电话号码类型不同但号码相同。然而,我们使用@DiffExclude
注解排除了address
字段,不参与比较。
查看System.out.println()
输出,我们可以看到所有差异已被找到:
firstName: John != Jane
lastName: Doe != Smith
age: 30 != 28
7. 总结
本文展示了如何使用Apache Commons Lang 3库中的DiffBuilder
和ReflectionDiffBuilder
类来比较Java对象。
这两个类易于使用,提供了方便的对象比较方式,尽管各自有优缺点。
通过本文中的示例,我们看到DiffBuilder
提供了更多的定制选项,更明确。然而,对于更复杂的对象,这可能会增加复杂性。
ReflectionDiffBuilder提供了更大的简洁性,但定制选项有限,并可能引入性能开销,因为它使用了反射。
文章中的代码可以在GitHub上找到。