equals和hashCode的區(qū)別和聯(lián)系

一龙巨、前言

前段時間使用list.remove(obj)的時候重寫了obj的equals方法嘀韧,因為list的remove是以equals來判斷標(biāo)準(zhǔn)的揪阶。但是,今天被公司的代碼掃描工具提示未重寫hashCode方法2锹蕖轨功!之前準(zhǔn)備面試時也多少看過,但是沒有細細研究過這個hashCode和equals到底背后是什么個關(guān)系容达,趁此機會古涧,總結(jié)一波。

本文章所用到的自定義測試對象類Stu:

public class Stu {
  private String name;
  private int age;

  Stu(String name, int age) {
    this.name = name;
    this.age = age;
  }

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public int getAge() {
    return age;
  }

  public void setAge(int age) {
    this.age = age;
  }
}

二花盐、equals的具體作用

首先要說的是equals是Object的方法羡滑,所以只能用于對象間菇爪,基本類型之間比較用“==”,反則他們的封裝類型可以用equals柒昏。

public static void main(String[] args) {
  Stu s1 = new Stu("張三", 18);
  Stu s2 = new Stu("張三", 18);
  System.out.println("stu:" + s1.equals(s2));

  Integer i1 = new Integer(18);
  Integer i2 = new Integer(18);
  System.out.println("Integer:" + i1.equals(i2));

  String str1 = "張三";
  String str2 = "張三";
  System.out.println("String:" + str1.equals(str2));
}

很簡單凳宙,可以得到下面的結(jié)果:

stu:false
Integer:true
String:true

通過idea工具可以看到各自的equals實現(xiàn)代碼:

Stu

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

Integer

public boolean equals(Object obj) {
    if (obj instanceof Integer) {
        return value == ((Integer)obj).intValue();
    }
    return false;
}

String

public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    if (anObject instanceof String) {
        String anotherString = (String) anObject;
        int n = value.length;
        if (n == anotherString.value.length) {
            char v1[] = value;
            char v2[] = anotherString.value;
            int i = 0;
            while (n-- != 0) {
                if (v1[i] != v2[i])
                        return false;
                i++;
            }
            return true;
        }
    }
    return false;
}

Stu因為沒有重寫equals方法,所以直接使用的父類Object的equals方法职祷,后面Integer和String都各自實現(xiàn)了自己的equals方法氏涩,所以Integer(基本類型)的equals實際上都是用的自己的實際值比較,String則是逐個char比較相等于否有梆。

三是尖、hashCode的具體作用

hashcode方法返回該對象的哈希碼值。支持該方法是為哈希表提供一些優(yōu)點泥耀,例如饺汹,java.util.Hashtable 提供的哈希表。

hashCode 的常規(guī)協(xié)定是:
在 Java 應(yīng)用程序執(zhí)行期間爆袍,在同一對象上多次調(diào)用 hashCode 方法時首繁,必須一致地返回相同的整數(shù),前提是對象上 equals 比較中所用的信息沒有被修改陨囊。從某一應(yīng)用程序的一次執(zhí)行到同一應(yīng)用程序的另一次執(zhí)行弦疮,該整數(shù)無需保持一致。

以下情況不 是必需的:如果根據(jù) equals(java.lang.Object) 方法蜘醋,兩個對象不相等胁塞,那么在兩個對象中的任一對象上調(diào)用 hashCode 方法必定會生成不同的整數(shù)結(jié)果。但是压语,程序員應(yīng)該知道啸罢,為不相等的對象生成不同整數(shù)結(jié)果可以提高哈希表的性能。

實際上胎食,由 Object 類定義的 hashCode 方法確實會針對不同的對象返回不同的整數(shù)扰才。(這一般是通過將該對象的內(nèi)部地址轉(zhuǎn)換成一個整數(shù)來實現(xiàn)的,但是 JavaTM 編程語言不需要這種實現(xiàn)技巧厕怜。)

當(dāng)equals方法被重寫時衩匣,通常有必要重寫 hashCode 方法,以維護 hashCode 方法的常規(guī)協(xié)定粥航,該協(xié)定聲明相等對象必須具有相等的哈希碼琅捏。

上面是引用的官方文檔上面的一段話,我們需要他說人話:

  1. 對象equals方法參與運算的自身屬性attr不能被修改递雀,并且同一個對象的hashCode值任何時候的返回值都應(yīng)該相等柄延;
  2. hashCode不等的兩個對象equals一定不相等,但是hashCode相等的兩個對象equals不一定相等缀程;
  3. 根據(jù)規(guī)定搜吧,重寫對象的equals方法必須重寫hashCode方法市俊,盡管不寫也能通過編譯;

這里引用網(wǎng)上一個很容易理解的例子:

hashcode是用來查找的滤奈,如果你學(xué)過數(shù)據(jù)結(jié)構(gòu)就應(yīng)該知道秕衙,在查找和排序這一章有
例如內(nèi)存中有這樣的位置
0 1 2 3 4 5 6 7
而我有個類,這個類有個字段叫id,我要把這個類存放在以上8個位置之一僵刮,如果不用hashcode而任意存放,那么當(dāng)查找時就需要到這八個位置里挨個去找鹦牛,或者用二分法一類的算法搞糕。
但如果用hashCode那就會使效率提高很多。
我們這個類中有個字段叫id,那么我們就定義我們的hashCode為id%8曼追,然后把我們的類存放在取得得余數(shù)那個位置窍仰。比如我們的ID為9,9除8的余數(shù)為1礼殊,那么我們就把該類存在1這個位置驹吮,如果ID是13,求得的余數(shù)是5晶伦,那么我們就把該類放在5這個位置碟狞。這樣,以后在查找該類時就可以通過ID除 8求余數(shù)直接找到存放的位置了婚陪。

但是如果兩個類有相同的hashCode怎么辦那(我們假設(shè)上面的類的id不是唯一的)族沃,例如9除以8和17除以8的余數(shù)都是1,那么這是不是合法的泌参,回答是:完全合法脆淹。那么如何判斷呢?在這個時候就需要定義equals了沽一。
也就是說盖溺,我們先通過 hashCode來判斷兩個類是否存放某個桶里,但這個桶里可能有很多類铣缠,那么我們就需要再通過 equals 來在這個桶里找到我們要的類烘嘱。
那么。重寫了equals()攘残,為什么還要重寫hashCode()呢拙友?
想想,你要在一個桶里找東西歼郭,你必須先要找到這個桶啊遗契,你不通過重寫hashCode()來找到桶,光重寫equals()有什么用啊病曾。

可能太過文本的東西沒有什么說服力牍蜂,那就來點干貨:

public static void main(String[] args) {
  Stu s1 = new Stu("張三", 18);
  Stu s2 = new Stu("張三", 18);
  System.out.println("stu:" + s1.equals(s2));

  Set<Stu> set = new HashSet<>();
  set.add(s1);
  System.out.println("s1 hashCode:" + s1.hashCode());
  System.out.println("add s1 size:" + set.size());
  set.add(s2);
  System.out.println("s2 hashCode:" + s2.hashCode());
  System.out.println("add s2 size::" + set.size());
}

輸出結(jié)果:

stu:false
s1 hashCode:1317241155
add s1 size:1
s2 hashCode:463175162
add s2 size::2

Java中的Set是不允許有重復(fù)元素的漾根,所以這里set的size由1變成了2,因為兩個Stu都是new出來的鲫竞,分配的地址不一樣辐怕,那么Set是通過equals來定義重復(fù)的嗎?

首先重寫Stu的equals方法:

@Override
public boolean equals(Object obj) {
  if (obj == null){
    return false;
  }
  if (obj.getClass() != getClass()){
    return false;
  }
  return ((Stu)obj).getName().equals(getName());
}

輸出結(jié)果:

stu:true
s1 hashCode:713679046
add s1 size:1
s2 hashCode:1107557627
add s2 size::2

重寫equals方法从绘,name相同就讓equals返回true了寄疏,但是Set的size還是發(fā)生了改變,就說明不是有equals方法來定義重復(fù)的僵井,現(xiàn)在僅僅重寫hashCode方法:

@Override
public int hashCode() {
  return getName().hashCode();
}

輸出結(jié)果:

stu:false
s1 hashCode:774889
add s1 size:1
s2 hashCode:774889
add s2 size::2

僅重寫了hashCode方法陕截,所以equals返回false,然后hashCode由name屬性的hashCode方法得到批什,所以hashCode相等农曲,但是Set的size還是改變了,這說明Set也不是僅僅依據(jù)hashCode來定義重復(fù)驻债。

那么現(xiàn)在將上述equals和hashCode兩者同時重寫乳规,輸出結(jié)果:

stu:true
s1 hashCode:774889
add s1 size:1
s2 hashCode:774889
add s2 size::1

結(jié)合上面引用的案例,可以類推合呐,hash類存儲結(jié)構(gòu)(HashSet暮的、HashMap等等)添加元素會有重復(fù)性校驗,校驗的方式就是先取hashCode判斷是否相等(找到對應(yīng)的位置淌实,該位置可能存在多個元素)青扔,然后再取equals方法比較(極大縮小比較范圍,高效判斷)翩伪,最終判定該存儲結(jié)構(gòu)中是否有重復(fù)元素微猖。

四、總結(jié)

  1. hashCode主要用于提升查詢效率缘屹,來確定在散列結(jié)構(gòu)中對象的存儲地址凛剥;
  2. 重寫equals()必須重寫hashCode(),二者參與計算的自身屬性字段應(yīng)該相同轻姿;
  3. hash類型的存儲結(jié)構(gòu)犁珠,添加元素重復(fù)性校驗的標(biāo)準(zhǔn)就是先取hashCode值,后判斷equals()互亮;
  4. equals()相等的兩個對象犁享,hashcode()一定相等;
  5. 反過來:hashcode()不等豹休,一定能推出equals()也不等炊昆;
  6. hashcode()相等,equals()可能相等,也可能不等凤巨。

五视乐、花邊:通用的hashCode重寫方案

初始化一個整形變量,為此變量賦予一個非零的常數(shù)值敢茁,比如int result = 17;
選取equals方法中用于比較的所有域佑淀,然后針對每個域的屬性進行計算:

  1. 如果是boolean值,則計算f ? 1:0
  2. 如果是byte\char\short\int,則計算(int)f
  3. 如果是long值彰檬,則計算(int)(f ^ (f >>> 32))
  4. 如果是float值伸刃,則計算Float.floatToIntBits(f)
  5. 如果是double值,則計算Double.doubleToLongBits(f)逢倍,然后返回的結(jié)果是long,再用規(guī)則(3)去處理long,得到int
  6. 如果是對象應(yīng)用奕枝,如果equals方法中采取遞歸調(diào)用的比較方式,那么hashCode中同樣采取遞歸調(diào)用hashCode的方式瓶堕。否則需要為這個域計算一個范式,比如當(dāng)這個域的值為null的時候症歇,那么hashCode 值為0
  7. 如果是數(shù)組郎笆,那么需要為每個元素當(dāng)做單獨的域來處理。如果你使用的是1.5及以上版本的JDK忘晤,那么沒必要自己去重新遍歷一遍數(shù)組宛蚓,java.util.Arrays.hashCode方法包含了8種基本類型數(shù)組和引用數(shù)組的hashCode計算,算法同上

給個簡單的例子:

@Override
public int hashCode() {
  int result = 17;
  result = 31 * result + getName().hashCode();
  return result;
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末设塔,一起剝皮案震驚了整個濱河市凄吏,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌闰蛔,老刑警劉巖痕钢,帶你破解...
    沈念sama閱讀 218,204評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異序六,居然都是意外死亡任连,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評論 3 395
  • 文/潘曉璐 我一進店門例诀,熙熙樓的掌柜王于貴愁眉苦臉地迎上來随抠,“玉大人,你說我怎么就攤上這事繁涂」八” “怎么了?”我有些...
    開封第一講書人閱讀 164,548評論 0 354
  • 文/不壞的土叔 我叫張陵扔罪,是天一觀的道長秉沼。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么氧猬? 我笑而不...
    開封第一講書人閱讀 58,657評論 1 293
  • 正文 為了忘掉前任背犯,我火速辦了婚禮,結(jié)果婚禮上盅抚,老公的妹妹穿的比我還像新娘漠魏。我一直安慰自己,他們只是感情好妄均,可當(dāng)我...
    茶點故事閱讀 67,689評論 6 392
  • 文/花漫 我一把揭開白布柱锹。 她就那樣靜靜地躺著,像睡著了一般丰包。 火紅的嫁衣襯著肌膚如雪禁熏。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,554評論 1 305
  • 那天邑彪,我揣著相機與錄音瞧毙,去河邊找鬼。 笑死寄症,一個胖子當(dāng)著我的面吹牛宙彪,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播有巧,決...
    沈念sama閱讀 40,302評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼释漆,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了篮迎?” 一聲冷哼從身側(cè)響起男图,我...
    開封第一講書人閱讀 39,216評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎甜橱,沒想到半個月后逊笆,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,661評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡岂傲,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,851評論 3 336
  • 正文 我和宋清朗相戀三年览露,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片譬胎。...
    茶點故事閱讀 39,977評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡差牛,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出堰乔,到底是詐尸還是另有隱情偏化,我是刑警寧澤,帶...
    沈念sama閱讀 35,697評論 5 347
  • 正文 年R本政府宣布镐侯,位于F島的核電站侦讨,受9級特大地震影響驶冒,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜韵卤,卻給世界環(huán)境...
    茶點故事閱讀 41,306評論 3 330
  • 文/蒙蒙 一骗污、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧沈条,春花似錦需忿、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至月而,卻和暖如春汗洒,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背父款。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評論 1 270
  • 我被黑心中介騙來泰國打工溢谤, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人憨攒。 一個月前我還...
    沈念sama閱讀 48,138評論 3 370
  • 正文 我出身青樓世杀,卻偏偏與公主長得像,于是被迫代替她去往敵國和親浓恶。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,927評論 2 355

推薦閱讀更多精彩內(nèi)容