轉(zhuǎn):https://www.cnblogs.com/skywang12345/p/3324958.html
1 equals() 的作用是什么沼瘫?
equals() 的作用是 用來判斷兩個(gè)對(duì)象是否相等扩劝。
equals() 定義在JDK的Object.java中键闺。通過判斷兩個(gè)對(duì)象的地址是否相等(即,是否是同一個(gè)對(duì)象)來區(qū)分它們是否相等屹培。源碼如下:
public boolean equals(Object obj) {
return (this == obj);
}
既然Object.java中定義了equals()方法,這就意味著所有的Java類都實(shí)現(xiàn)了equals()方法,所有的類都可以通過equals()去比較兩個(gè)對(duì)象是否相等欧啤。 但是,我們已經(jīng)說過启上,使用默認(rèn)的“equals()”方法邢隧,等價(jià)于“==”方法。因此冈在,我們通常會(huì)重寫equals()方法:若兩個(gè)對(duì)象的內(nèi)容相等倒慧,則equals()方法返回true;否則,返回fasle纫谅。
下面根據(jù)“類是否覆蓋equals()方法”炫贤,將它分為2類。
(01) 若某個(gè)類沒有覆蓋equals()方法付秕,當(dāng)它的通過equals()比較兩個(gè)對(duì)象時(shí)兰珍,實(shí)際上是比較兩個(gè)對(duì)象是不是同一個(gè)對(duì)象。這時(shí)询吴,等價(jià)于通過“==”去比較這兩個(gè)對(duì)象掠河。
(02) 我們可以覆蓋類的equals()方法,來讓equals()通過其它方式比較兩個(gè)對(duì)象是否相等猛计。通常的做法是:若兩個(gè)對(duì)象的內(nèi)容相等唠摹,則equals()方法返回true;否則奉瘤,返回fasle跃闹。
下面,舉例對(duì)上面的2種情況進(jìn)行說明毛好。
①. “沒有覆蓋equals()方法”的情況
代碼如下 (EqualsTest1.java):
import java.util.*;
import java.lang.Comparable;
/**
* @desc equals()的測試程序望艺。
*
* @author skywang
* @emai kuiwu-wang@163.com
*/
public class EqualsTest1{
public static void main(String[] args) {
// 新建2個(gè)相同內(nèi)容的Person對(duì)象,
// 再用equals比較它們是否相等
Person p1 = new Person("eee", 100);
Person p2 = new Person("eee", 100);
System.out.printf("%s\n", p1.equals(p2));
}
/**
* @desc Person類肌访。
*/
private static class Person {
int age;
String name;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String toString() {
return name + " - " +age;
}
}
}
運(yùn)行結(jié)果:
false
結(jié)果分析:
我們通過 p1.equals(p2) 來“比較p1和p2是否相等時(shí)”找默。實(shí)際上,調(diào)用的Object.java的equals()方法吼驶,即調(diào)用的 (p1==p2) 惩激。它是比較“p1和p2是否是同一個(gè)對(duì)象”。
而由 p1 和 p2 的定義可知蟹演,它們雖然內(nèi)容相同风钻;但它們是兩個(gè)不同的對(duì)象!因此酒请,返回結(jié)果是false骡技。
②. "覆蓋equals()方法"的情況
我們修改上面的EqualsTest1.java:覆蓋equals()方法。
代碼如下 (EqualsTest2.java):
import java.util.*;
import java.lang.Comparable;
/**
* @desc equals()的測試程序羞反。
*
* @author skywang
* @emai kuiwu-wang@163.com
*/
public class EqualsTest2{
public static void main(String[] args) {
// 新建2個(gè)相同內(nèi)容的Person對(duì)象布朦,
// 再用equals比較它們是否相等
Person p1 = new Person("eee", 100);
Person p2 = new Person("eee", 100);
System.out.printf("%s\n", p1.equals(p2));
}
/**
* @desc Person類。
*/
private static class Person {
int age;
String name;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String toString() {
return name + " - " +age;
}
/**
* @desc 覆蓋equals方法
*/
@Override
public boolean equals(Object obj){
if(obj == null){
return false;
}
//如果是同一個(gè)對(duì)象返回true昼窗,反之返回false
if(this == obj){
return true;
}
//判斷是否類型相同
if(this.getClass() != obj.getClass()){
return false;
}
Person person = (Person)obj;
return name.equals(person.name) && age==person.age;
}
}
}
運(yùn)行結(jié)果:
true
結(jié)果分析:
我們在EqualsTest2.java 中重寫了Person的equals()函數(shù):當(dāng)兩個(gè)Person對(duì)象的 name 和 age 都相等是趴,則返回true。因此澄惊,運(yùn)行結(jié)果返回true唆途。
講到這里富雅,順便說一下java對(duì)equals()的要求。有以下幾點(diǎn):
- 對(duì)稱性:如果x.equals(y)返回是"true"肛搬,那么y.equals(x)也應(yīng)該返回是"true"吹榴。
- 反射性:x.equals(x)必須返回是"true"。
- 類推性:如果x.equals(y)返回是"true"滚婉,而且y.equals(z)返回是"true"图筹,那么z.equals(x)也應(yīng)該返回是"true"。
- 一致性:如果x.equals(y)返回是"true"让腹,只要x和y內(nèi)容一直不變远剩,不管你重復(fù)x.equals(y)多少次,返回都是"true"骇窍。
- 非空性瓜晤,x.equals(null),永遠(yuǎn)返回是"false"腹纳;x.equals(和x不同類型的對(duì)象)永遠(yuǎn)返回是"false"痢掠。
2 equals() 與 == 的區(qū)別是什么?
== : 它的作用是判斷兩個(gè)對(duì)象的地址是不是相等嘲恍。即足画,判斷兩個(gè)對(duì)象是不試同一個(gè)對(duì)象。
equals() : 它的作用也是判斷兩個(gè)對(duì)象是否相等佃牛。但它一般有兩種使用情況(前面第1部分已詳細(xì)介紹過):
情況1淹辞,類沒有覆蓋equals()方法。則通過equals()比較該類的兩個(gè)對(duì)象時(shí)俘侠,等價(jià)于通過“==”比較這兩個(gè)對(duì)象象缀。
情況2,類覆蓋了equals()方法爷速。一般央星,我們都覆蓋equals()方法來兩個(gè)對(duì)象的內(nèi)容相等;若它們的內(nèi)容相等惫东,則返回true(即莉给,認(rèn)為這兩個(gè)對(duì)象相等)。
下面凿蒜,通過示例比較它們的區(qū)別禁谦。
import java.util.*;
import java.lang.Comparable;
/**
* @desc equals()的測試程序。
*
* @author skywang
* @emai kuiwu-wang@163.com
*/
public class EqualsTest3{
public static void main(String[] args) {
// 新建2個(gè)相同內(nèi)容的Person對(duì)象废封,
// 再用equals比較它們是否相等
Person p1 = new Person("eee", 100);
Person p2 = new Person("eee", 100);
System.out.printf("p1.equals(p2) : %s\n", p1.equals(p2));
System.out.printf("p1==p2 : %s\n", p1==p2);
}
/**
* @desc Person類。
*/
private static class Person {
int age;
String name;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String toString() {
return name + " - " +age;
}
/**
* @desc 覆蓋equals方法
*/
@Override
public boolean equals(Object obj){
if(obj == null){
return false;
}
//如果是同一個(gè)對(duì)象返回true丧蘸,反之返回false
if(this == obj){
return true;
}
//判斷是否類型相同
if(this.getClass() != obj.getClass()){
return false;
}
Person person = (Person)obj;
return name.equals(person.name) && age==person.age;
}
}
}
運(yùn)行結(jié)果:
p1.equals(p2) : true
p1==p2 : false
結(jié)果分析:
在EqualsTest3.java 中:
(01) p1.equals(p2)
這是判斷p1和p2的內(nèi)容是否相等漂洋。因?yàn)镻erson覆蓋equals()方法遥皂,而這個(gè)equals()是用來判斷p1和p2的內(nèi)容是否相等,恰恰p1和p2的內(nèi)容又相等刽漂;因此演训,返回true。
(02) p1==p2
這是判斷p1和p2是否是同一個(gè)對(duì)象贝咙。由于它們是各自新建的兩個(gè)Person對(duì)象样悟;因此,返回false庭猩。
3 hashCode() 的作用是什么窟她?
hashCode() 的作用是獲取哈希碼,也稱為散列碼蔼水;它實(shí)際上是返回一個(gè)int整數(shù)震糖。這個(gè)哈希碼的作用是確定該對(duì)象在哈希表中的索引位置。
hashCode() 定義在JDK的Object.java中趴腋,這就意味著Java中的任何類都包含有hashCode() 函數(shù)吊说。
雖然,每個(gè)Java類都包含hashCode() 函數(shù)优炬。但是颁井,僅僅當(dāng)創(chuàng)建并某個(gè)“類的散列表”(關(guān)于“散列表”見下面說明)時(shí),該類的hashCode() 才有用(作用是:確定該類的每一個(gè)對(duì)象在散列表中的位置蠢护;其它情況下(例如蚤蔓,創(chuàng)建類的單個(gè)對(duì)象,或者創(chuàng)建類的對(duì)象數(shù)組等等)糊余,類的hashCode() 沒有作用秀又。
上面的散列表,指的是:Java集合中本質(zhì)是散列表的類贬芥,如HashMap吐辙,Hashtable,HashSet蘸劈。
也就是說:**hashCode() 在散列表中才有用昏苏,在其它情況下沒用。**在散列表中hashCode() 的作用是獲取對(duì)象的散列碼威沫,進(jìn)而確定該對(duì)象在散列表中的位置贤惯。
OK!至此棒掠,我們搞清楚了:hashCode()的作用是獲取散列碼孵构。但是,散列碼是用來干什么的呢烟很?為什么散列表需要散列碼呢颈墅?要解決這些問題蜡镶,就需要理解散列表!關(guān)于散列表的內(nèi)容恤筛,非三言兩語道的明白官还;大家可以通過下面幾篇文章來學(xué)習(xí):
[轉(zhuǎn)載] 散列表(Hash Table)從理論到實(shí)用(上)
[轉(zhuǎn)載] 散列表(Hash Table)從理論到實(shí)用(中)
[轉(zhuǎn)載] 散列表(Hash Table)從理論到實(shí)用(下)
為了能理解后面的內(nèi)容,這里簡單的介紹一下散列碼的作用毒坛。
我們都知道望伦,散列表存儲(chǔ)的是鍵值對(duì)(key-value),它的特點(diǎn)是:能根據(jù)“鍵”快速的檢索出對(duì)應(yīng)的“值”煎殷。這其中就利用到了散列碼屯伞!
散列表的本質(zhì)是通過數(shù)組實(shí)現(xiàn)的。當(dāng)我們要獲取散列表中的某個(gè)“值”時(shí)蝌数,實(shí)際上是要獲取數(shù)組中的某個(gè)位置的元素愕掏。而數(shù)組的位置,就是通過“鍵”來獲取的顶伞;更進(jìn)一步說饵撑,數(shù)組的位置,是通過“鍵”對(duì)應(yīng)的散列碼計(jì)算得到的唆貌。
下面滑潘,我們以HashSet為例,來深入說明hashCode()的作用锨咙。
假設(shè)语卤,HashSet中已經(jīng)有1000個(gè)元素。當(dāng)插入第1001個(gè)元素時(shí)酪刀,需要怎么處理粹舵?因?yàn)镠ashSet是Set集合,它不允許有重復(fù)元素骂倘。
“將第1001個(gè)元素逐個(gè)的和前面1000個(gè)元素進(jìn)行比較”眼滤?顯然,這個(gè)效率是相等低下的历涝。散列表很好的解決了這個(gè)問題诅需,它根據(jù)元素的散列碼計(jì)算出元素在散列表中的位置,然后將元素插入該位置即可荧库。對(duì)于相同的元素堰塌,自然是只保存了一個(gè)。
由此可知分衫,若兩個(gè)元素相等场刑,它們的散列碼一定相等;但反過來確不一定丐箩。在散列表中:
1摇邦、如果兩個(gè)對(duì)象相等恤煞,那么它們的hashCode()值一定要相同屎勘;
2施籍、如果兩個(gè)對(duì)象hashCode()相等,它們并不一定相等概漱。
注意:這是在散列表中的情況丑慎。在非散列表中一定如此!
對(duì)“hashCode()的作用”就談這么多瓤摧。
4 hashCode() 和 equals() 之間有什么聯(lián)系竿裂?
接下面,我們討論另外一個(gè)話題照弥。網(wǎng)上很多文章將 hashCode() 和 equals 關(guān)聯(lián)起來腻异,有的講的不透徹,有誤導(dǎo)讀者的嫌疑这揣。在這里,我自己梳理了一下 “hashCode() 和 equals()的關(guān)系”给赞。
我們以“類的用途”來將“hashCode() 和 equals()的關(guān)系”分2種情況來說明。
-
第一種 不會(huì)創(chuàng)建“類對(duì)應(yīng)的散列表”
這里所說的“不會(huì)創(chuàng)建類對(duì)應(yīng)的散列表”是說:我們不會(huì)在HashSet, Hashtable, HashMap等等這些本質(zhì)是散列表的數(shù)據(jù)結(jié)構(gòu)中残邀,用到該類柑蛇。例如,不會(huì)創(chuàng)建該類的HashSet集合空免。 在這種情況下粘我,該類的“hashCode() 和 equals() ”沒有半毛錢關(guān)系的! 這種情況下征字,equals() 用來比較該類的兩個(gè)對(duì)象是否相等匙姜。而hashCode() 則根本沒有任何作用,所以氮昧,不用理會(huì)hashCode()浦楣。
下面咪辱,我們通過示例查看類的兩個(gè)對(duì)象相等 以及 不等時(shí)hashCode()的取值。
源碼如下 (NormalHashCodeTest.java):
import java.util.*;
import java.lang.Comparable;
/**
* @desc 比較equals() 返回true 以及 返回false時(shí)历恐, hashCode()的值专筷。
*
* @author skywang
* @emai kuiwu-wang@163.com
*/
public class NormalHashCodeTest{
public static void main(String[] args) {
// 新建2個(gè)相同內(nèi)容的Person對(duì)象,
// 再用equals比較它們是否相等
Person p1 = new Person("eee", 100);
Person p2 = new Person("eee", 100);
Person p3 = new Person("aaa", 200);
System.out.printf("p1.equals(p2) : %s; p1(%d) p2(%d)\n", p1.equals(p2), p1.hashCode(), p2.hashCode());
System.out.printf("p1.equals(p3) : %s; p1(%d) p3(%d)\n", p1.equals(p3), p1.hashCode(), p3.hashCode());
}
/**
* @desc Person類吮旅。
*/
private static class Person {
int age;
String name;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String toString() {
return name + " - " +age;
}
/**
* @desc 覆蓋equals方法
*/
public boolean equals(Object obj){
if(obj == null){
return false;
}
//如果是同一個(gè)對(duì)象返回true味咳,反之返回false
if(this == obj){
return true;
}
//判斷是否類型相同
if(this.getClass() != obj.getClass()){
return false;
}
Person person = (Person)obj;
return name.equals(person.name) && age==person.age;
}
}
}
運(yùn)行結(jié)果:
p1.equals(p2) : true; p1(1169863946) p2(1901116749)
p1.equals(p3) : false; p1(1169863946) p3(2131949076)
從結(jié)果也可以看出:p1和p2相等的情況下,hashCode()也不一定相等匪凉。
-
第二種 會(huì)創(chuàng)建“類對(duì)應(yīng)的散列表”
這里所說的“會(huì)創(chuàng)建類對(duì)應(yīng)的散列表”是說:我們會(huì)在HashSet, Hashtable, HashMap等等這些本質(zhì)是散列表的數(shù)據(jù)結(jié)構(gòu)中捺檬,用到該類。例如聂受,會(huì)創(chuàng)建該類的HashSet集合烤镐。 在這種情況下,該類的“hashCode() 和 equals() ”是有關(guān)系的: 1)炮叶、如果兩個(gè)對(duì)象相等,那么它們的hashCode()值一定相同祟辟。 這里的相等是指侣肄,通過equals()比較兩個(gè)對(duì)象時(shí)返回true。 2)吼具、如果兩個(gè)對(duì)象hashCode()相等,它們并不一定相等拗盒。 因?yàn)樵谏⒘斜碇新嘀洌琱ashCode()相等赞弥,即兩個(gè)鍵值對(duì)的哈希值相等。然而哈希值相等绽左,并不一定能得出鍵值對(duì)相等拼窥。補(bǔ)充說一句:“兩個(gè)不同的鍵值對(duì),哈希值相等”鲁纠,這就是哈希沖突。 此外情龄,在這種情況下捍壤。若要判斷兩個(gè)對(duì)象是否相等,除了要覆蓋equals()之外专酗,也要覆蓋hashCode()函數(shù)盗扇。否則,equals()無效疗隶。
例如,創(chuàng)建Person類的HashSet集合允青,必須同時(shí)覆蓋Person類的equals() 和 hashCode()方法。
如果單單只是覆蓋equals()方法法牲。我們會(huì)發(fā)現(xiàn),equals()方法沒有達(dá)到我們想要的效果拒垃。
參考代碼 (ConflictHashCodeTest1.java):
import java.util.*;
import java.lang.Comparable;
/**
* @desc 比較equals() 返回true 以及 返回false時(shí)瓷蛙, hashCode()的值。
*
* @author skywang
* @emai kuiwu-wang@163.com
*/
public class ConflictHashCodeTest1{
public static void main(String[] args) {
// 新建Person對(duì)象横堡,
Person p1 = new Person("eee", 100);
Person p2 = new Person("eee", 100);
Person p3 = new Person("aaa", 200);
// 新建HashSet對(duì)象
HashSet set = new HashSet();
set.add(p1);
set.add(p2);
set.add(p3);
// 比較p1 和 p2冠桃, 并打印它們的hashCode()
System.out.printf("p1.equals(p2) : %s; p1(%d) p2(%d)\n", p1.equals(p2), p1.hashCode(), p2.hashCode());
// 打印set
System.out.printf("set:%s\n", set);
}
/**
* @desc Person類。
*/
private static class Person {
int age;
String name;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String toString() {
return "("+name + ", " +age+")";
}
/**
* @desc 覆蓋equals方法
*/
@Override
public boolean equals(Object obj){
if(obj == null){
return false;
}
//如果是同一個(gè)對(duì)象返回true胸蛛,反之返回false
if(this == obj){
return true;
}
//判斷是否類型相同
if(this.getClass() != obj.getClass()){
return false;
}
Person person = (Person)obj;
return name.equals(person.name) && age==person.age;
}
}
}
運(yùn)行結(jié)果:
p1.equals(p2) : true; p1(1169863946) p2(1690552137)
set:[(eee, 100), (eee, 100), (aaa, 200)]
結(jié)果分析:
我們重寫了Person的equals()葬项。但是迹蛤,很奇怪的發(fā)現(xiàn):HashSet中仍然有重復(fù)元素:p1 和 p2。為什么會(huì)出現(xiàn)這種情況呢笤受?
這是因?yàn)殡m然p1 和 p2的內(nèi)容相等箩兽,但是它們的hashCode()不等;所以汗贫,HashSet在添加p1和p2的時(shí)候落包,認(rèn)為它們不相等。
下面咐蝇,我們同時(shí)覆蓋equals() 和 hashCode()方法。
參考代碼 (ConflictHashCodeTest2.java):
import java.util.*;
import java.lang.Comparable;
/**
* @desc 比較equals() 返回true 以及 返回false時(shí)抹腿, hashCode()的值。
*
* @author skywang
* @emai kuiwu-wang@163.com
*/
public class ConflictHashCodeTest2{
public static void main(String[] args) {
// 新建Person對(duì)象崇败,
Person p1 = new Person("eee", 100);
Person p2 = new Person("eee", 100);
Person p3 = new Person("aaa", 200);
Person p4 = new Person("EEE", 100);
// 新建HashSet對(duì)象
HashSet set = new HashSet();
set.add(p1);
set.add(p2);
set.add(p3);
set.add(p4);
// 比較p1 和 p2后室, 并打印它們的hashCode()
System.out.printf("p1.equals(p2) : %s; p1(%d) p2(%d)\n", p1.equals(p2), p1.hashCode(), p2.hashCode());
// 比較p1 和 p4混狠, 并打印它們的hashCode()
System.out.printf("p1.equals(p4) : %s; p1(%d) p4(%d)\n", p1.equals(p4), p1.hashCode(), p4.hashCode());
// 打印set
System.out.printf("set:%s\n", set);
}
/**
* @desc Person類。
*/
private static class Person {
int age;
String name;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String toString() {
return name + " - " +age;
}
/**
* @desc重寫hashCode
*/
@Override
public int hashCode(){
int nameHash = name.toUpperCase().hashCode();
return nameHash ^ age;
}
/**
* @desc 覆蓋equals方法
*/
@Override
public boolean equals(Object obj){
if(obj == null){
return false;
}
//如果是同一個(gè)對(duì)象返回true松申,反之返回false
if(this == obj){
return true;
}
//判斷是否類型相同
if(this.getClass() != obj.getClass()){
return false;
}
Person person = (Person)obj;
return name.equals(person.name) && age==person.age;
}
}
}
運(yùn)行結(jié)果:
p1.equals(p2) : true; p1(68545) p2(68545)
p1.equals(p4) : false; p1(68545) p4(68545)
set:[eee - 100, EEE - 100, aaa - 200]
結(jié)果分析:
這下,equals()生效了舅逸,HashSet中沒有重復(fù)元素。
比較p1和p2坠七,我們發(fā)現(xiàn):它們的hashCode()相等旗笔,通過equals()比較它們也返回true蝇恶。所以,p1和p2被視為相等撮弧。
比較p1和p4贿衍,我們發(fā)現(xiàn):雖然它們的hashCode()相等;但是释树,通過equals()比較它們返回false奢啥。所以署浩,p1和p4被視為不相等筋栋。