1. 比較Java中的對象

在 Java 中,對象可以通過引用和值進行比較。

比較參考資料

如果兩個變量在內存中指向同一個對象,那麼這些變量中存儲的引用是相等的。如果使用相等運算符 ( ==) 比較這些變量,結果為真,並且該結果有意義。這裡的一切都很簡單。

代碼 控制台輸出
Integer a = 5;
Integer b = a;
System.out.println(a == b);


true

按價值比較

但是您經常會遇到兩個變量引用兩個相同的不同對象的情況。例如,包含相同文本的兩個不同字符串對象。

要確定不同的對像是否相同,請使用equals()方法。例如:

代碼 控制台輸出
String a = new String("Hello");
String b = new String("Hello");
System.out.println(a == b);
System.out.println(a.equals(b));


false
true

equals方法不限於String類。每個班級都有。

甚至是您自己編寫的類,這就是原因。



2.Object

Java 中的所有類都繼承該類Object。Java 的創建者想出了這種方法。

而如果一個類繼承了該類Object,那麼它就獲得了該類的所有方法Object。這是繼承的主要結果。

換句話說,每個類都有Object類的方法,即使他們的代碼沒有提到它們。

這些繼承的方法包括與對像比較相關的方法。這些是equals()hashCode()方法。

代碼 實際上,這就是我們將擁有的:
class Person
{
   String name;
   int age;
}
class Person extends Object
{
   String name;
   int age;

   public boolean equals(Object obj)
   {
      return this == obj;
   }

   public int hashCode()
   {
      return address_of_object_in_memory; // This is the default implementation, but there may be a different implementation
   }
}

在上面的示例中,我們創建了一個Person帶有 name 和 age 參數的簡單類,但沒有一個方法。但是因為所有的類都繼承了這個Object類,所以這個Person類自動有兩個方法:

方法 描述
boolean equals(Object obj)
比較當前對象和傳遞的對象
int hashCode()
返回當前對象的哈希碼

事實證明,絕對每個對像都有equals方法,不同類型的對象可以相互比較。這樣的代碼將編譯並完美運行。

代碼 控制台輸出
Integer a = 5;
String s = "Hello";
System.out.println(a.equals(s));
System.out.println(s.equals(a));


false
false
Object a = new Integer(5);
Object b = new Integer(5);
System.out.println(a.equals(b)) ;


true

3.equals()方法

equals()方法繼承自Object該類,實現了比較當前對象與傳遞的對象的最簡單算法:它只是比較對對象的引用。

Person如果您只是比較變量而不是調用方法,您會得到相同的結果equals()。例子:

代碼 控制台輸出
Person a = new Person();
a.name = "Steve";

Person b = new Person();
b.name = "Steve";

System.out.println(a == b);
System.out.println(a.equals(b));






false
false

equals調用該方法時a,它只是將存儲在變量中的引用a與存儲在b變量中的引用進行比較。

但是,類的比較工作不同String。為什麼?

因為創建該類的人String編寫了他們自己的方法實現equals()

equals()方法的實現

現在讓我們在類中編寫我們自己的 equals 方法的實現Person。我們將考慮 4 個主要案例。

重要的:
無論哪個類重寫equals方法,它總是以一個Object對像作為參數

場景 1:調用該方法的同一對equals像也傳遞給該equals方法。如果當前對象和傳遞對象的引用相等,則該方法必須返回true。一個對像等於它自己。

在代碼中它看起來像這樣:

代碼 描述
public boolean equals(Object obj)
{
   if (this == obj)
    return true;

   // The rest of the code of the equals method
}


比較參考資料

場景 2null傳遞給equals方法——我們沒有什麼可比較的。調用該方法的對象equals肯定不為null,所以我們需要false在這種情況下返回。

在代碼中它看起來像這樣:

代碼 描述
public boolean equals(Object obj)
{
   if (this == obj)
      return true;

   if (obj == null)
      return false;

   // The rest of the code of the equals method
}


比較引用


是傳遞過來的對象null嗎?

場景 3:將對不是 a 的對象的引用傳遞Person給該equals方法。對像是否Person等於非Person對象?這是一個由課程開發者Person決定的問題,無論他或她想要什麼。

但通常對象必須屬於同一類才能被視為相等。因此,如果類的對像以外的東西Person被傳遞給我們的 equals 方法,那麼我們將始終返回false。如何檢查對象的類型?沒錯——通過使用instanceof運算符。

這是我們的新代碼的樣子:

代碼 描述
public boolean equals(Object obj)
{
   if (this == obj)
      return true;

   if (obj == null)
      return false;

   if (!(obj instanceof Person))
      return false;

   // The rest of the code of the equals method
}


比較引用


是傳遞過來的對象null嗎?


如果傳遞的對像不是Person

4.比較兩個Person對象

我們最終得到了什麼?如果我們已經到達方法的末尾,那麼我們有一個Person不是的對象引用null。所以我們將其轉換為a Person,並比較兩個對象的相關內部數據。這是我們的第四個場景

代碼 描述
public boolean equals(Object obj)
{
   if (this == obj)
      return true;

   if (obj == null)
      return false;

   if (!(obj instanceof Person))
      return false;

   Person person = (Person) obj;

   // The rest of the code of the equals method
}


比較引用


是傳遞過來的對象null嗎?


如果傳遞的對像不是Person


類型轉換

你如何比較兩個Person對象?如果他們具有相同的名字 ( name) 和年齡 ( age),則它們是相等的。最終代碼將如下所示:

代碼 描述
public boolean equals(Object obj)
{
   if (this == obj)
      return true;

   if (obj == null)
      return false;

   if (!(obj instanceof Person))
      return false;

   Person person = (Person) obj;

   return this.name == person.name && this.age == person.age;
}


比較引用


是傳遞過來的對象null嗎?


如果傳遞的對像不是Person


類型轉換

但這還不是全部。

首先,name字段是a String,所以需要通過調用equals方法來比較name字段。

this.name.equals(person.name)

其次,該name字段可能是null:在這種情況下,您不能調用equals它。您需要額外檢查null

this.name != null && this.name.equals(person.name)

也就是說,如果名稱字段null在兩個Person對像中,那麼名稱仍然相同。

第四種情況的代碼可能如下所示:

Person person = (Person) obj;

if (this.age != person.age)
   return false;

if (this.name == null)
   return person.name == null;

return this.name.equals(person.name);


如果年齡不相等,
則立即return false

Ifthis.name等於null,使用該方法進行比較沒有意義equals。這裡要么第二個name字段等於null,要么不等於。

使用 方法比較兩個名稱字段equals


5.hashCode()方法

除了equals旨在對兩個對象的所有字段執行詳細比較的方法之外,還有另一種方法可用於不精確但非常快速的比較:hashCode().

想像一下,您正在按字母順序對包含數千個單詞的列表進行排序,並且您需要反复比較單詞對。單詞很長,由很多字母組成。一般來說,這樣的比較會耗費很長時間。

但它可以加速。假設我們有以不同字母開頭的單詞——很明顯它們是不同的。但如果它們以相同的字母開頭,那麼我們還不能說結果會是什麼:這些詞可能相同或不同。

hashCode()方法使用類似的原理工作。如果你在一個對像上調用它,它會返回一些數字——類似於單詞中的第一個字母。該數字具有以下屬性:

  • 相同的對象總是具有相同的哈希碼
  • 不同的對象可以有相同的哈希碼,或者它們的哈希碼可以不同
  • 如果對像有不同的哈希碼,那麼對象肯定是不同的

為了更清楚地說明這一點,讓我們用文字重新定義這些屬性:

  • 相同的單詞總是有相同的首字母。
  • 不同單詞的首字母可以相同,也可以不同
  • 如果單詞的首字母不同,則單詞肯定不同

最後一個屬性用於加速對像比較:

首先,計算兩個對象的哈希碼。如果這些hashcode不一樣,那麼對象肯定是不一樣的,沒必要再去比較了。

但是如果哈希碼相同,那麼我們仍然需要使用 equals 方法來比較對象。



6. 代碼契約

上述行為必須由 Java 中的所有類實現。在編譯期間,無法檢查對像是否被正確比較。

Java 程序員有一個普遍的共識,如果他們自己編寫 equals() 方法的實現並因此覆蓋標準實現(在類中),他們也必須按照上述規則編寫自己的方法Object實現hashCode()使滿意。

這種安排稱為合同

如果您只在您的班級中實施該方法equals()或僅實施該hashCode()方法,那麼您就嚴重違反了合同(您違反了協議)。不要這樣做。

如果其他程序員使用您的代碼,它可能無法正常工作。此外,您將使用依賴於遵守上述合同的代碼。

重要的!

所有Java集合在查找元素時,都是先比較對象的hashcode,然後才使用方法進行比較equals

這意味著,如果您為自己的類提供了一個equals方法,但您沒有編寫自己的hashCode()方法或未正確實現它,那麼集合可能無法與您的對像一起正常工作。

例如,您可能將一個對象添加到列表中,然後使用該方法搜索它contains(),但集合可能找不到您的對象。