Java中如何判斷兩個對象是否相等(Java equals and ==)

原文:https://www.dutycode.com/post-140.html

如何判斷兩個對象相等,這個問題實際上可以看做是如何對equals方法和hashcode方法的理解。

從以下幾個點來理解equals和hashCode方法:

1、equals的作用及與==的區別。
2、hashcode的作用及與equals的關係。

1、equals的作用及與==的區別。
equals被用來判斷兩個對象是否相等。
equals通常用來比較兩個對象的內容是否相等,==用來比較兩個對象的地址是否相等。
equals方法默認等同於“==”
Object類中的equals方法定義爲判斷兩個對象的地址是否相等(可以理解成是否是同一個對象),地址相等則認爲是對象相等。這也就意味着,我們新建的所有類如果沒有複寫equals方法,那麼判斷兩個對象是否相等時就等同於“==”,也就是兩個對象的地址是否相等。
Object類中equals的方法實現如下:

public boolean equals(Object obj) {
       
 return (this == obj);
    }
但在我們的實際開發中,通常會認爲兩個對象的內容相等時,則兩個對象相等,equals返回true。對象內容不同,則返回false。
所以可以總結爲兩種情況
1、類未複寫equals方法,則使用equals方法比較兩個對象時,相當於==比較,即兩個對象的地址是否相等。地址相等,返回true,地址不相等,返回false。
2、類複寫equals方法,比較兩個對象時,則走複寫之後的判斷方式。通常,我們會將equals複寫成:當兩個對象內容相同時,則equals返回true,內容不同時,返回false。
舉個例子:
public class EqualTest {
public static void main(String[] args) {
Person
 p1 = new Person(10,"張三");
Person
 p2 = new Person(10,"張三");
System.
out.println(p1.equals(p2));
}
}
class Person{
int age;
String
 name;
public Person(int age, String name) {
super();
this.age = age;
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Person未複寫equals方法,則默認使用了Object中的equals,即爲兩個對象(p1和p2)的內存地址判斷,p1和p2很明顯內存地址不同,所以輸出結果很明顯爲false。
如果我們複寫equals方法呢?我們認爲名字和年齡一樣的就是同一個人,那麼p1和p2都表示10歲的張三,這兩個對象應該是相等的。複寫的equals方法如下:

@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Person
 other = (Person) obj;
if (age != other.age)
return false;
if (name == null) {
if (other.name != null)
return false;
}
 else if (!name.equals(other.name))
return false;
return true;
}
同樣的,執行上述用例,得到的結果是true。
BTW:如果equals方法返回true,那麼==是否也是true?
不一定是true。equals返回true有兩種可能,一種是兩個對象地址相同,一種是兩個對象內容相同。當內容相同時,地址可能不同,所以==比較的結果可能爲false。
我們把main方法加上對==的判斷,如下:

public static void main(String[] args) {
Person
 p1 = new Person(10,"張三");
Person
 p2 = new Person(10,"張三");
System.
out.println(p1.equals(p2));
System.
out.println(p1 == p2);
}
輸出結果很明顯 p1==p2的結果是false。

補充Java中對Equals的要求:
1. 對稱性:如果x.equals(y)返回是"true",那麼y.equals(x)也應該返回是"true"。
2. 反射性:x.equals(x)必須返回是"true"。
3. 類推性:如果x.equals(y)返回是"true",而且y.equals(z)返回是"true",那麼z.equals(x)也應該返回是"true"。
4. 一致性:如果x.equals(y)返回是"true",只要x和y內容一直不變,不管你重複x.equals(y)多少次,返回都是"true"。
5. 非空性,x.equals(null),永遠返回是"false";x.equals(和x不同類型的對象)永遠返回是"false"。

2、hashCode的作用及與equals的關係。
hashCode的作用是用來獲取哈希碼,也可以稱作散列碼。實際返回值爲一個int型數據。用於確定對象在哈希表中的位置。
Object中有hashcode方法,也就意味着所有的類都有hashCode方法。
但是,hashcode只有在創建某個類的散列表的時候纔有用,需要根據hashcode值確認對象在散列表中的位置,但在其他情況下沒用。
java中本質上是散列表的類常見的有HashMap,HashSet,HashTable
所以,如果一個對象一定不會在散列表中使用,那麼是沒有必要複寫hashCode方法的。但一般情況下我們還是會複寫hashCode方法,因爲誰能保證這個對象不會出現再hashMap等中呢?
舉個例子:
兩個對象equals相等的時候,hashcode並不一定相等。

public class EqualTest {
public static void main(String[] args) {
Person
 p1 = new Person(10, "張三");
Person
 p2 = new Person(10, "張三");
System.
out.println(
"p1.equals(p2)=" + p1.equals(p2) + ", p1.hashcode=" + p1.hashCode() + ", p2.hashcode=" +p2.hashCode());
}
}
class Person {
int age;
String
 name;
public Person(int age, String name) {
super();
this.age = age;
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Person
 other = (Person) obj;
if (age != other.age)
return false;
if (name == null) {
if (other.name != null)
return false;
}
 else if (!name.equals(other.name))
return false;
return true;
}
}
Person沒有複寫hashCode方法,使用Object默認的hashCode實現,輸出結果如下:

p1.equals(p2)=true, p1.hashcode=246688959, p2.hashcode=1457895203
從結果可以看出,equals雖然相同,但是p1和p2的hashcode並不相同。

如果Person用於散列表的類中呢,這裏用HashSet來做測試。
public class EqualTest {
public static void main(String[] args) {
Person
 p1 = new Person(10, "張三");
Person
 p2 = new Person(10, "張三");
System.
out.println(
"p1.equals(p2)=" + p1.equals(p2) + ", p1.hashcode=" + p1.hashCode() + ", p2.hashcode=" +p2.hashCode());
HashSet<Person>
 set = new HashSet<Person>();
set.add(p1);
set.add(p2);
System.
out.println(set);
}
}
class Person {
int age;
String
 name;
public Person(int age, String name) {
super();
this.age = age;
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Person
 other = (Person) obj;
if (age != other.age)
return false;
if (name == null) {
if (other.name != null)
return false;
}
 else if (!name.equals(other.name))
return false;
return true;
}
@Override
public String toString() {
return "Person [age=" + age + ", name=" + name + "]";
}
}

輸出結果

p1.equals(p2)=true, p1.hashcode=246688959, p2.hashcode=1457895203
[Person [age=10, name=張三], Person [age=10, name=張三]]

p1和p2的equals相同,我們認爲是兩個對象相等,但是這兩個對象竟然同時出現再hashSet中,hashSet中是不會出現兩個相同的元素的。 
那問題在哪裏?
hashset在添加一個元素的時候,會做如下判斷:
1、如果添加元素的hashcode相等並且 對象equals爲true或對象== 時,則認爲是同一個元素,不添加到新元素中。
2、如果不符合上述條件,則認爲是一個新元素,添加到set中。
所以,雖然p1和p2equals比較時相等,但是hashcode並不一樣,所以在往set中添加的時候認爲是兩個不同的元素,所以纔會出現了p1和p2同時在set中的情況。

我們改進下,複寫一下hashcode方法,如下:

class Person {
int age;
String
 name;
public Person(int age, String name) {
super();
this.age = age;
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + age;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Person
 other = (Person) obj;
if (age != other.age)
return false;
if (name == null) {
if (other.name != null)
return false;
}
 else if (!name.equals(other.name))
return false;
return true;
}
@Override
public String toString() {
return "Person [age=" + age + ", name=" + name + "]";
}
}

重新執行結果:

p1.equals(p2)=true, p1.hashcode=776160, p2.hashcode=776160
[Person [age=10, name=張三]]

於是看到set中僅有一個Person值了。

補充幾點:
1、新建一個類,尤其是業務相關的對象類的時候,最好複寫equals方法。
2、複寫equals方法時,同時記着要複寫hashCode方法,誰能保證說這個對象一定不會出現在hashMap中呢?如果你用的是eclipse的自動代碼生成,你會發現eclipse中複寫equals和hashCode是在一起的。

引申出幾個經常在面試中問到的問題:
     1、兩個對象,如果a.equals(b)==true,那麼a和b是否相等?
          相等,但地址不一定相等。
     2、兩個對象,如果hashcode一樣,那麼兩個對象是否相等?
          不一定相等,判斷兩個對象是否相等,需要判斷equals是否爲true。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章