使用 Apache Commons Lang 3 比較 Java 中的對象
1. 概述
比較對像是 Java 以及許多其他程式語言的核心概念之一。這是處理排序、搜尋和過濾資料時的一個基本概念,在程式設計的各個方面都起著至關重要的作用。
Java 中的物件比較可以透過實作比較邏輯或使用具有物件比較功能的函式庫來手動完成。可以使用各種函式庫來比較 Java 中的對象,例如 JaVers 或 Apache Commons Lang 3,我們將在本文中介紹它們。
2. 關於 Apache Commons Lang 3
Apache Commons Lang 3 代表 Apache Commons Lang 函式庫的 3.0 版本,它提供了許多功能。
我們將探索[DiffBuilder](https://commons.apache.org/proper/commons-lang/apidocs/org/apache/commons/lang3/builder/DiffBuilder.html)
類別來比較並取得相同類型的兩個物件之間的差異。產生的差異用[DiffResult](https://commons.apache.org/proper/commons-lang/apidocs/org/apache/commons/lang3/builder/DiffResult.html)
類別表示。
DiffBuilder
還有一個替代方案ReflectionDiffBuilder
它有相同的目的,但它是基於反射,而DiffBuilder
不是。
3.Maven依賴
要使用 Apache Commons Lang 3,我們首先需要新增Maven 依賴項:
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.13.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
物件。我們首先使用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();
}
這裡,我們使用DiffBuilder
來實作compare()
方法。當使用append()
方法產生DiffBuilder
時,我們可以精確控制哪些欄位將用於比較。
出於演示目的,在比較嵌套的PhoneNumber
物件時,我們省略了type
欄位的比較,因此具有相同number
和不同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
物件。我們首先使用compare()
方法定義一個PersonReflectionDiffBuilder
類別:
public static DiffResult compare(Person first, Person second) {
return new ReflectionDiffBuilder<>(first, second, ToStringStyle.SHORT_PREFIX_STYLE).build();
}
這裡,我們使用ReflectionDiffBuilder
來實作compare()
方法。無需附加各個字段進行比較,因為所有非靜態和非瞬態字段都是使用反射發現的。
在此範例中,發現的欄位為firstName
、 lastName
、 age
、 phoneNumbers
和address
。在內部, ReflectionDiffBuilder
使用DiffBuilder,
並且它是使用發現的欄位建構的。
假設我們想從比較中排除特定的已發現欄位。在這種情況下,我們可以使用@DiffExclude
註解來標記我們希望從ReflectionDiffBuilder
的使用中排除的欄位。
由於我們的Person
類別具有包含嵌套物件的複雜結構,為了確保ReflectionDiffBuilder
正確識別差異,我們必須實作equals()
和hashCode()
方法。
出於演示目的,我們將使用@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
}
我們還將省略在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());
}
在此範例中,被比較的人有不同的名字、姓氏和地址。電話號碼的類型不同,但數量相同。但是,我們在address
字段上添加了@DiffExclude
註釋,以將其從比較中排除。
如果我們看一下System.out.println()
輸出,我們可以看到所有差異都已找到:
firstName: John != Jane
lastName: Doe != Smith
age: 30 != 28
七、結論
在本文中,我們示範如何使用 Apache Commons Lang 3 函式庫中的DiffBuilder
和ReflectionDiffBuilder
來比較 Java 物件。
這兩個類別都易於使用,並提供了一種比較物件的便捷方法,儘管每個類別都有優點和缺點。
透過本文中的範例我們已經看到, DiffBuilder
提供了更多的自訂功能並且更加明確。儘管如此,它可能會導致更複雜物件的複雜性增加。
ReflectionDiffBuilder
提供了更多簡單性,但自訂選項有限,並且可能會引入效能開銷,因為它使用反射。
本文中的程式碼可以在 GitHub 上找到。