如何判斷兩個對象相等,這個問題實際上可以看做是如何對equals方法和hashcode方法的理解。
從以下幾個點來理解equals和hashCode方法:
1、equals的作用及與==的區(qū)別。
2猖凛、hashcode的作用及與equals的關(guān)系。
1绪穆、equals的作用及與==的區(qū)別辨泳。
equals被用來判斷兩個對象是否相等。
equals通常用來比較兩個對象的內(nèi)容是否相等玖院,==用來比較兩個對象的地址是否相等菠红。
equals方法默認(rèn)等同于“==”
Object類中的equals方法定義為判斷兩個對象的地址是否相等(可以理解成是否是同一個對象),地址相等則認(rèn)為是對象相等司恳。這也就意味著途乃,我們新建的所有類如果沒有復(fù)寫equals方法绍傲,那么判斷兩個對象是否相等時就等同于“==”扔傅,也就是兩個對象的地址是否相等。
Object類中equals的方法實現(xiàn)如下:
public?boolean?equals(Object?obj) {
? ? ? ? return?(this?==?obj);
}
但在我們的實際開發(fā)中烫饼,通常會認(rèn)為兩個對象的內(nèi)容相等時猎塞,則兩個對象相等,equals返回true杠纵。對象內(nèi)容不同荠耽,則返回false。
所以可以總結(jié)為兩種情況
1比藻、類未復(fù)寫equals方法铝量,則使用equals方法比較兩個對象時,相當(dāng)于==比較银亲,即兩個對象的地址是否相等慢叨。地址相等,返回true务蝠,地址不相等拍谐,返回false。
2、類復(fù)寫equals方法轩拨,比較兩個對象時践瓷,則走復(fù)寫之后的判斷方式。通常亡蓉,我們會將equals復(fù)寫成:當(dāng)兩個對象內(nèi)容相同時晕翠,則equals返回true,內(nèi)容不同時寸宵,返回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;
? ? Stringname;
? ? 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 StringgetName() {
return name;
? ? }
public void setName(String name) {
this.name = name;
? ? }
}
Person未復(fù)寫equals方法,則默認(rèn)使用了Object中的equals梯影,即為兩個對象(p1和p2)的內(nèi)存地址判斷巫员,p1和p2很明顯內(nèi)存地址不同,所以輸出結(jié)果很明顯為false甲棍。
如果我們復(fù)寫equals方法呢简识?我們認(rèn)為名字和年齡一樣的就是同一個人,那么p1和p2都表示10歲的張三感猛,這兩個對象應(yīng)該是相等的七扰。復(fù)寫的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;
}
同樣的,執(zhí)行上述用例陪白,得到的結(jié)果是true颈走。
BTW:如果equals方法返回true,那么==是否也是true咱士?
不一定是true立由。equals返回true有兩種可能,一種是兩個對象地址相同序厉,一種是兩個對象內(nèi)容相同锐膜。當(dāng)內(nèi)容相同時,地址可能不同弛房,所以==比較的結(jié)果可能為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);
}
輸出結(jié)果很明顯 p1==p2的結(jié)果是false文捶。
補充Java中對Equals的要求:
1.?對稱性:如果x.equals(y)返回是"true"荷逞,那么y.equals(x)也應(yīng)該返回是"true"。
2.?反射性:x.equals(x)必須返回是"true"粹排。
3.?類推性:如果x.equals(y)返回是"true"种远,而且y.equals(z)返回是"true",那么z.equals(x)也應(yīng)該返回是"true"恨搓。
4.?一致性:如果x.equals(y)返回是"true"院促,只要x和y內(nèi)容一直不變筏养,不管你重復(fù)x.equals(y)多少次,返回都是"true"常拓。
5.?非空性渐溶,x.equals(null),永遠(yuǎn)返回是"false"弄抬;x.equals(和x不同類型的對象)永遠(yuǎn)返回是"false"茎辐。
2、hashCode的作用及與equals的關(guān)系掂恕。
hashCode的作用是用來獲取哈希碼拖陆,也可以稱作散列碼。實際返回值為一個int型數(shù)據(jù)懊亡。用于確定對象在哈希表中的位置依啰。
Object中有hashcode方法,也就意味著所有的類都有hashCode方法店枣。
但是速警,hashcode只有在創(chuàng)建某個類的散列表的時候才有用,需要根據(jù)hashcode值確認(rèn)對象在散列表中的位置鸯两,但在其他情況下沒用闷旧。
java中本質(zhì)上是散列表的類常見的有HashMap,HashSet钧唐,HashTable
所以忙灼,如果一個對象一定不會在散列表中使用,那么是沒有必要復(fù)寫hashCode方法的钝侠。但一般情況下我們還是會復(fù)寫hashCode方法该园,因為誰能保證這個對象不會出現(xiàn)再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 StringgetName() {
return name;
? ? }
public void setName(String name) {
this.name = name;
? ? }
@Override
publicboolean 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沒有復(fù)寫hashCode方法父腕,使用Object默認(rèn)的hashCode實現(xiàn)弱匪,輸出結(jié)果如下:
p1.equals(p2)=true, p1.hashcode=246688959, p2.hashcode=1457895203
從結(jié)果可以看出,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 set =new HashSet();
? ? ? ? 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 StringgetName() {
return name;
? ? }
public void setName(String name) {
this.name = name;
? ? }
@Overridepublicboolean 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;
? ? }
@Overridepublic StringtoString() {
return "Person [age=" +age +", name=" +name +"]";
? ? }
}
輸出結(jié)果
p1.equals(p2)=true, p1.hashcode=246688959, p2.hashcode=1457895203
[Person [age=10, name=張三], Person [age=10, name=張三]]
p1和p2的equals相同帘饶,我們認(rèn)為是兩個對象相等,但是這兩個對象竟然同時出現(xiàn)再hashSet中群扶,hashSet中是不會出現(xiàn)兩個相同的元素的及刻。?
那問題在哪里镀裤?
hashset在添加一個元素的時候,會做如下判斷:
1缴饭、如果添加元素的hashcode相等并且 對象equals為true或?qū)ο?= 時暑劝,則認(rèn)為是同一個元素,不添加到新元素中颗搂。
2担猛、如果不符合上述條件,則認(rèn)為是一個新元素丢氢,添加到set中傅联。
所以,雖然p1和p2equals比較時相等疚察,但是hashcode并不一樣蒸走,所以在往set中添加的時候認(rèn)為是兩個不同的元素,所以才會出現(xiàn)了p1和p2同時在set中的情況貌嫡。
我們改進下载碌,復(fù)寫一下hashcode方法,如下:
class Person {
int age;
? ? Stringname;
? ? 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 StringgetName() {
return name;
? ? }
public void setName(String name) {
this.name = name;
? ? }
@Overridepublicint hashCode() {
final int prime =31;
? ? ? ? int result =1;
? ? ? ? result = prime * result +age;
? ? ? ? result = prime * result + ((name ==null) ?0 :name.hashCode());
? ? ? ? return result;
? ? }
@Overridepublicboolean 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;
? ? }
@Overridepublic StringtoString() {
return "Person [age=" +age +", name=" +name +"]";
? ? }
}
重新執(zhí)行結(jié)果:
p1.equals(p2)=true, p1.hashcode=776160, p2.hashcode=776160
[Person [age=10, name=張三]]
于是看到set中僅有一個Person值了衅枫。
補充幾點:
1嫁艇、新建一個類,尤其是業(yè)務(wù)相關(guān)的對象類的時候弦撩,最好復(fù)寫equals方法步咪。
2、復(fù)寫equals方法時益楼,同時記著要復(fù)寫hashCode方法猾漫,誰能保證說這個對象一定不會出現(xiàn)在hashMap中呢?如果你用的是eclipse的自動代碼生成感凤,你會發(fā)現(xiàn)eclipse中復(fù)寫equals和hashCode是在一起的悯周。
引申出幾個經(jīng)常在面試中問到的問題:
? ? ?1、兩個對象陪竿,如果a.equals(b)==true禽翼,那么a和b是否相等?
? ? ? ? ? 相等族跛,但地址不一定相等闰挡。
? ? ?2、兩個對象礁哄,如果hashcode一樣长酗,那么兩個對象是否相等?
? ? ? ? ? 不一定相等桐绒,判斷兩個對象是否相等夺脾,需要判斷equals是否為true之拨。