Java各種比較對象的方式的對比總結

一、==和!=操作符

讓我們從==和!=開始可以分別判斷兩個Java對象是否相同的操作符。

1.1 原始類型(Primitives)

對於原始類型,相同意味著具有相等的值:

assertThat(1 == 1).isTrue();

感謝自動拆箱,在將原語值與其包裝類型對應值進行比較時,也可以這樣做:

Integer a = new Integer(1);
assertThat(1 == a).isTrue();

如果兩個整數的值不同,==運算符將返回false,而!=運算符將返回true。

1.2 對象比較

假設我們要比較兩個具有相同值的整數包裝類型:

Integer a = new Integer(1);
Integer b = new Integer(1);

assertThat(a == b).isFalse();

通過比較兩個對象,這些對象的值不是1,而是它們在堆棧中的內存地址,因為這兩個對象都是使用new操作符創建的。如果我們把a分配給b,我們會得到不同的結果:

Integer a = new Integer(1);
Integer b = a;

assertThat(a == b).isTrue();

現在,讓我們看看使用Integer#valueOf factory方法時會發生什麼:

Integer a = Integer.valueOf(1);
Integer b = Integer.valueOf(1);

assertThat(a == b).isTrue();

在這種情況下,它們被認為是相同的。這是因為valueOf()方法將整數存儲在緩存中,以避免創建太多具有相同值的包裝器對象。因此,該方法為兩個調用返回相同的整數實例。

對字符串也是一樣:

assertThat("Hello!" == "Hello!").isTrue();

但是,如果它們是使用new操作符創建的,那麼它們就不一樣瞭。最後,兩個空引用被認為是相同的,而任何非空對象將被認為與空對象不同:

assertThat(null == null).isTrue();
assertThat("Hello!" == null).isFalse();

當然,相等運算符的行為可能是有限的。如果我們想比較兩個映射到不同地址的對象,並根據它們的內部狀態將它們視為相等,那該怎麼辦?我們將在下一節中看到如何操作。

二、Object的equals方法

現在,讓我們用equals()方法討論一個更廣泛的相等概念。這個方法是在Object類中定義的,以便每個Java對象都繼承它。默認情況下,它的實現比較對象內存地址,因此它的工作方式與==運算符相同。但是,我們可以重寫這個方法來定義相等對我們的對象意味著什麼。

首先,讓我們看看它對現有對象(如Integer)的表現:

Integer a = new Integer(1);
Integer b = new Integer(1);

assertThat(a.equals(b)).isTrue();

當兩個對象相同時,該方法仍然返回true。我們應該註意,我們可以傳遞一個空對象作為方法的參數,但是當然,不能作為調用方法的對象。我們可以對自己的對象使用equals()方法。假設我們有一個Person類:

public class Person {
    private String firstName;
    private String lastName;

    public Person(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }
}

我們可以重寫該類的equals()方法,以便根據兩個人的內部詳細信息進行比較:

@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    Person that = (Person) o;
    return firstName.equals(that.firstName) &&
      lastName.equals(that.lastName);
}

三、Objects的靜態方法equals

現在讓我們看看Objects的equals靜態方法。我們前面提到過,不能使用null作為第一個對象的值,否則會拋出NullPointerException。Objects助手類的equals()方法解決瞭這個問題。它接受兩個參數並比較它們,同時處理空值。

讓我們再次比較Person對象:

Person joe = new Person("Joe", "Portman");
Person joeAgain = new Person("Joe", "Portman");
Person natalie = new Person("Natalie", "Portman");

assertThat(Objects.equals(joe, joeAgain)).isTrue();
assertThat(Objects.equals(joe, natalie)).isFalse();

如前所述,該方法處理空值。因此,如果兩個參數都為null,則返回true;如果隻有一個參數為null,則返回false。這真的很方便。假設我們想給Person類添加一個可選的出生日期:

public Person(String firstName, String lastName, LocalDate birthDate) {
    this(firstName, lastName);
    this.birthDate = birthDate;
}

然後,我們必須更新equals()方法,但是要處理Null。我們可以將此條件添加到equals()方法中:

birthDate == null ? that.birthDate == null : birthDate.equals(that.birthDate);

但是,如果我們在類中添加許多可為null的字段,它可能會變得非常混亂。在equals()實現中使用Objects#equals方法更加簡潔,並且提高瞭可讀性:

Objects.equals(birthDate, that.birthDate);

四、Comparable接口

比較邏輯也可用於給對象排序。可比較接口允許我們通過確定一個對象是大於、等於還是小於另一個對象來定義對象之間的順序。

Compariable接口是泛型的,隻有一個方法compareTo(),該方法接受泛型類型的參數並返回int。如果當前值小於參數,則返回負值;如果它們相等,則返回0;否則返回正值。

比方說,在我們的Person類中,我們希望按Person對象的姓氏進行比較:

public class Person implements Comparable<Person> {
    //...

    @Override
    public int compareTo(Person o) {
        return this.lastName.compareTo(o.lastName);
    }
}

如果使用姓氏大於此的人調用compareTo()方法,則返回負int;如果姓氏相同,則返回零;否則返回正int。

五、Comparator接口

Comparator接口是泛型的,並且有一個compare方法,該方法接受該泛型類型的兩個參數並返回一個整數。我們在前面的可比較接口中已經看到瞭這種模式。

比較器相似;但是,它與類的定義是分離的。因此,我們可以為一個類定義任意多個比較器,其中我們隻能提供一個可比較的實現。

假設我們有一個在網頁中有一個展示人信息的表格,我們想讓用戶能夠按名字而不是姓氏對他們進行排序。如果我們還想保持當前的實現,那麼使用Comparable是不可能的,但是我們可以實現自己的比較器。

讓我們創建一個Person Comparator,它將隻根據他們的名字進行比較:

Comparator<Person> compareByFirstNames = Comparator.comparing(Person::getFirstName);

現在讓我們用這個比較器對一組人進行排序:

Person joe = new Person("Joe", "Portman");
Person allan = new Person("Allan", "Dale");

List<Person> people = new ArrayList<>();
people.add(joe);
people.add(allan);

people.sort(compareByFirstNames);

assertThat(people).containsExactly(allan, joe);

在compareTo()實現中,可以使用Comparator接口上的其他方法:

@Override
public int compareTo(Person o) {
    return Comparator.comparing(Person::getLastName)
      .thenComparing(Person::getFirstName)
      .thenComparing(Person::getBirthDate, Comparator.nullsLast(Comparator.naturalOrder()))
      .compare(this, o);
}

在這種情況下,我們首先比較姓,然後比較名。然後,我們比較出生日期,但是由於它們是可空的,我們必須說明如何處理它,所以我們給出瞭第二個參數,告訴它們應該根據它們的自然順序進行比較,但是空值最後才是。

六、使用Apache Commons

現在讓我們看看apachecommons庫。首先,讓我們導入Maven依賴項:

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

6.1 ObjectUtils的notEqual方法

首先,我們來討論ObjectUtils#notEqual方法。它需要兩個對象參數,根據它們自己的equals()方法實現來確定它們是否相等。它還處理空值。

讓我們重新使用我們的字符串示例:

String a = new String("Hello!");
String b = new String("Hello World!");

assertThat(ObjectUtils.notEqual(a, b)).isTrue();

應該註意,ObjectUtils有一個equals()方法。但是,自從Java7出現Objects#equals之後,這種方法就被棄用瞭。

6.2 ObjectUtils的compare方法

現在,讓我們用ObjectUtils#compare方法比較對象順序。它是一個泛型方法,它接受該泛型類型的兩個可比較參數並返回一個整數。

讓我們再看看如何使用字符串:

String first = new String("Hello!");
String second = new String("How are you?");

assertThat(ObjectUtils.compare(first, second)).isNegative();

默認情況下,該方法通過將空值視為更大值來處理空值。它提供瞭一個重載版本,它提供瞭一個佈爾參數來反轉該行為,並將它們考慮得更小。

七、使用Guava

現在,我們來看看Guava。Guava 是 Google 的一個開源項目,包含許多 Google 核心 Java 常用庫,如:集合 [collections] 、緩存 [caching] 、原生類型支持 [primitives support] 、並發庫 [concurrency libraries] 、通用註解 [common annotations] 、字符串處理 [string processing] 與 I/O 等。

首先,讓我們導入依賴項:

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>29.0-jre</version>
</dependency>

7.1 Objects的equal方法

與apache commons庫類似,Google為我們提供瞭一種方法來確定兩個對象是否相等,objects#equal。盡管它們有不同的實現,但它們返回相同的結果:

String a = new String("Hello!");
String b = new String("Hello!");

assertThat(Objects.equal(a, b)).isTrue();

盡管它沒有被標記為deprecated,但是這個方法的JavaDoc說它應該被視為deprecated,因為java7提供瞭Objects#equals方法。

7.2 Comparison方法

現在,Guava庫沒有提供一個方法來比較兩個對象(我們將在下一節中看到如何實現這一點),但它確實提供瞭比較原始值的方法。讓我們看看Ints helper類的compare()方法是如何工作的:

assertThat(Ints.compare(1, 2)).isNegative();

通常,如果第一個參數小於、等於或大於第二個參數,則返回一個整數,該整數可能為負、零或正。除瞭bytes之外,所有基元類型都有類似的方法。

7.3 ComparisonChain類

最後,Guava庫提供瞭ComparisonChain類,它允許我們通過一系列比較來比較兩個對象。我們可以很容易地比較兩個人的名字和姓氏:

Person natalie = new Person("Natalie", "Portman");
Person joe = new Person("Joe", "Portman");

int comparisonResult = ComparisonChain.start()
  .compare(natalie.getLastName(), joe.getLastName())
  .compare(natalie.getFirstName(), joe.getFirstName())
  .result();

assertThat(comparisonResult).isPositive();

底層比較是使用compareTo()方法實現的,因此傳遞給compare()方法的參數必須是原始類型或可比較的對象。

八、完整代碼

Java對象比較

到此這篇關於Java各種比較對象的方式的對比總結的文章就介紹到這瞭,更多相關Java比較對象的方式內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: