Java中hashCode和equals相關問題闡述

本文主要針對如下三個問題進行解釋:

  • 默認情況下hashCode相同是不是意味著equals方法相等肆汹?
  • 默認情況下equals方法相等是不是意味著hashCode相同硬萍?
  • 重寫equals方法是不是需要重寫hashCode方法匹中?為什么继谚?

默認情況下hashCode相同是不是意味著equals方法相等和問題?equals方法相等是不是意味著hashCode相同?

之所以將這兩個問題放在一起篓吁,是因為兩個問題可以聯(lián)系在一起回答隙袁,在Object類中的hashCode和equals方法中已經(jīng)有了該問題的答案

image.png

圖中紅色區(qū)域的意思為:如果兩個對象根據(jù)equals方法判定相等滑臊,那么這兩個對象的hashCode方法必定是相同的integer的整形值口芍。其中暗含了兩層意思:

  1. equals相等的兩個對象,其hashCode必定相等
  2. 通過equals判定前雇卷,必定有hashCode值比較判斷的步驟

看到這里我們自然疑惑hashCode方法是如何得到某個對象的hash值的鬓椭,我們再看如下這句話

image.png

其中說到hashCode方法一種典型的實現(xiàn)是將對象在堆內(nèi)的地址通過某種手段轉成一個integer整形值小染,但是該方法是native修飾的,需要通過查閱openjdk的源碼得到贮折,查閱相關資料得到真正對應的hashCode生成方法如下

intptr_t ObjectSynchronizer::FastHashCode (Thread * Self, oop obj) {
  if (UseBiasedLocking) {
    // NOTE: many places throughout the JVM do not expect a safepoint
    // to be taken here, in particular most operations on perm gen
    // objects. However, we only ever bias Java instances and all of
    // the call sites of identity_hash that might revoke biases have
    // been checked to make sure they can handle a safepoint. The
    // added check of the bias pattern is to avoid useless calls to
    // thread-local storage.
    if (obj->mark()->has_bias_pattern()) {
      // Box and unbox the raw reference just in case we cause a STW safepoint.
      Handle hobj (Self, obj) ;
      // Relaxing assertion for bug 6320749.
      assert (Universe::verify_in_progress() ||
              !SafepointSynchronize::is_at_safepoint(),
             biases should not be seen by VM thread here);
      BiasedLocking::revoke_and_rebias(hobj, false, JavaThread::current());
      obj = hobj() ;
      assert(!obj->mark()->has_bias_pattern(), biases should be revoked by now);
    }
  }
 
  // hashCode() is a heap mutator ...
  // Relaxing assertion for bug 6320749.
  assert (Universe::verify_in_progress() ||
          !SafepointSynchronize::is_at_safepoint(), invariant) ;
  assert (Universe::verify_in_progress() ||
          Self->is_Java_thread() , invariant) ;
  assert (Universe::verify_in_progress() ||
         ((JavaThread *)Self)->thread_state() != _thread_blocked, invariant) ;
 
  ObjectMonitor* monitor = NULL;
  markOop temp, test;
  intptr_t hash;
  markOop mark = ReadStableMark (obj);
 
  // object should remain ineligible for biased locking
  assert (!mark->has_bias_pattern(), invariant) ;
 
  if (mark->is_neutral()) {
    hash = mark->hash();              // this is a normal header
    if (hash) {                       // if it has hash, just return it
      return hash;
    }
    hash = get_next_hash(Self, obj);  // allocate a new hash code
    temp = mark->copy_set_hash(hash); // merge the hash code into header
    // use (machine word version) atomic operation to install the hash
    test = (markOop) Atomic::cmpxchg_ptr(temp, obj->mark_addr(), mark);
    if (test == mark) {
      return hash;
    }
    // If atomic operation failed, we must inflate the header
    // into heavy weight monitor. We could add more code here
    // for fast path, but it does not worth the complexity.
  } else if (mark->has_monitor()) {
    monitor = mark->monitor();
    temp = monitor->header();
    assert (temp->is_neutral(), invariant) ;
    hash = temp->hash();
    if (hash) {
      return hash;
    }
    // Skip to the following code to reduce code size
  } else if (Self->is_lock_owned((address)mark->locker())) {
    temp = mark->displaced_mark_helper(); // this is a lightweight monitor owned
    assert (temp->is_neutral(), invariant) ;
    hash = temp->hash();              // by current thread, check if the displaced
    if (hash) {                       // header contains hash code
      return hash;
    }
    // WARNING:
    //   The displaced header is strictly immutable.
    // It can NOT be changed in ANY cases. So we have
    // to inflate the header into heavyweight monitor
    // even the current thread owns the lock. The reason
    // is the BasicLock (stack slot) will be asynchronously
    // read by other threads during the inflate() function.
    // Any change to stack may not propagate to other threads
    // correctly.
  }
 
  // Inflate the monitor to set hash code
  monitor = ObjectSynchronizer::inflate(Self, obj);
  // Load displaced header and check it has hash code
  mark = monitor->header();
  assert (mark->is_neutral(), invariant) ;
  hash = mark->hash();
  if (hash == 0) {
    hash = get_next_hash(Self, obj);
    temp = mark->copy_set_hash(hash); // merge hash code into header
    assert (temp->is_neutral(), invariant) ;
    test = (markOop) Atomic::cmpxchg_ptr(temp, monitor, mark);
    if (test != mark) {
      // The only update to the header in the monitor (outside GC)
      // is install the hash code. If someone add new usage of
      // displaced header, please update this code
      hash = test->hash();
      assert (test->is_neutral(), invariant) ;
      assert (hash != 0, Trivial unexpected object/monitor header usage.);
    }
  }
  // We finally get the hash  
  return hash;

重寫equals方法是不是需要重寫hashCode方法氧映?為什么?

首先該問題的答案仍然在Object中的equals方法注釋中寫的很清楚脱货,如下圖所示

image.png

按紅框處的官方解釋來說岛都,只要equals方法被重寫了就必須重寫hashCode方法,此處也解釋了“必須”的原因振峻,要維持hashCode方法的contract約定臼疫,hashCode方法中申明了相同的對象必須有相同的hash code。
為了進一步加深對該“必須”的理解扣孟,這里又從兩個方面舉例說明:
<li> 自己創(chuàng)建一個自定義對象烫堤,只重寫equals方法而不重寫hashCode方法,看看有什么問題
<li> 從HashMap源碼的角度分析一下凤价,如果只重寫equals而不重寫hashCode有什么問題

<p> 自己創(chuàng)建一個Person對象鸽斟,有pname和age字段,如下所示

public class Person implements Serializable {
    private static final long serialVersionUID = 7592930394427200495L;

    private String pname;
    private int age;

    public Person() {

    }

    public Person(String pname, int age) {
        this.pname = pname;
        this.age = age;
    }

    public String getPname() {
        return pname;
    }

    public void setPname(String pname) {
        this.pname = pname;
    }

    public int getAge() {
        return age;
    }

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

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Person)) return false;

        Person person = (Person) o;

        if (age != person.age) return false;
        return !(pname != null ? !pname.equals(person.pname) : person.pname != null);

    }

//    @Override
//    public int hashCode() {
//        int result = pname != null ? pname.hashCode() : 0;
//        result = 31 * result + age;
//        return result;
//    }
}

<p> 進行測試

@Test
    public void fun() {
        Person p1 = new Person("lisi", 15);
        Person p2 = new Person("lisi", 15);
        Assert.assertEquals(false, p1.equals(p2));
    }

<p> 結果如下

image.png

p1和p2在java堆中肯定分屬不同的Person實例對象利诺,其地址必定不相同富蓄,但因為我們僅僅重寫了equals方法,只對pname和age的值進行了比對從而導致了結果的錯誤慢逾,如果重寫了hashCode方法立倍,根據(jù)兩個實例地址的相關算法進行判斷就會避免這個問題

同樣的我們再來分析HashMap中的一段源碼再次說明hashCode和equals方法同時重寫的重要性灭红,其中的關鍵點在于put操作時的邏輯

image.png

當新元素放入HashMap時,會首先計算出該元素對應放在哪一個Entry鏈表上(HashMap原理不了解的請查閱相關文檔)口注,然后通過和鏈表上的每一個元素比較变擒,來判斷新加入元素是否是重復元素,而判斷重復元素的思路就體現(xiàn)了兩個方法協(xié)同的重要性寝志,首先會判斷兩個元素的hash值是否相等娇斑,再判斷兩個元素equals是否相等,設想一下材部,如果沒有重寫元素的hashCode方法毫缆,那么就有可能存在這種可能,兩個元素不等败富,但hash code相等,重寫的equals也相等(如Person例中)摩窃,從而導致錯誤的覆蓋

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末兽叮,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子猾愿,更是在濱河造成了極大的恐慌鹦聪,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蒂秘,死亡現(xiàn)場離奇詭異泽本,居然都是意外死亡,警方通過查閱死者的電腦和手機姻僧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進店門规丽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人撇贺,你說我怎么就攤上這事赌莺。” “怎么了松嘶?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵艘狭,是天一觀的道長。 經(jīng)常有香客問我翠订,道長巢音,這世上最難降的妖魔是什么械媒? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任如失,我火速辦了婚禮,結果婚禮上劣挫,老公的妹妹穿的比我還像新娘似谁。我一直安慰自己歧寺,他們只是感情好燥狰,可當我...
    茶點故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著斜筐,像睡著了一般龙致。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上顷链,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天目代,我揣著相機與錄音,去河邊找鬼嗤练。 笑死榛了,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的煞抬。 我是一名探鬼主播霜大,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼革答!你這毒婦竟也來了战坤?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤残拐,失蹤者是張志新(化名)和其女友劉穎途茫,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體溪食,經(jīng)...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡囊卜,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了错沃。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片栅组。...
    茶點故事閱讀 39,795評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖枢析,靈堂內(nèi)的尸體忽然破棺而出笑窜,到底是詐尸還是另有隱情,我是刑警寧澤登疗,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布排截,位于F島的核電站,受9級特大地震影響辐益,放射性物質發(fā)生泄漏断傲。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一智政、第九天 我趴在偏房一處隱蔽的房頂上張望认罩。 院中可真熱鬧,春花似錦续捂、人聲如沸垦垂。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽劫拗。三九已至间校,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間页慷,已是汗流浹背憔足。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留酒繁,地道東北人滓彰。 一個月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像州袒,于是被迫代替她去往敵國和親揭绑。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,724評論 2 354

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

  • 從三月份找實習到現(xiàn)在郎哭,面了一些公司他匪,掛了不少,但最終還是拿到小米彰居、百度诚纸、阿里撰筷、京東陈惰、新浪、CVTE毕籽、樂視家的研發(fā)崗...
    時芥藍閱讀 42,246評論 11 349
  • 1. Java基礎部分 基礎部分的順序:基本語法抬闯,類相關的語法,內(nèi)部類的語法关筒,繼承相關的語法溶握,異常的語法,線程的語...
    子非魚_t_閱讀 31,630評論 18 399
  • 一、基本數(shù)據(jù)類型 注釋 單行注釋:// 區(qū)域注釋:/* */ 文檔注釋:/** */ 數(shù)值 對于byte類型而言...
    龍貓小爺閱讀 4,261評論 0 16
  • 本來是在岸邊欣賞美景的袍榆,卻不料掉進了水里胀屿,不會游泳,水在窒息的時候包雀,只能胡亂撲騰宿崭,可能抓傷了小魚,可能拽傷了蘆葦才写。...
    好似心中有魔法閱讀 179評論 0 2
  • 文|趙曉璃 寫在前面的話: 不知你是否有這樣的感受吆鹤,那就是到了三十歲,突然會感到莫名的恐慌蜕劝。 誠如一位咨詢者感慨的...
    趙曉璃閱讀 2,519評論 10 64