equals()方法與hashCode()方法

在Java中,equals()方法與hashCode方法定義在java.lang.Object類中,意味著所有的類都會默認(rèn)有這兩個方法豌鸡。我們來看一下這兩個方法在Object類中的定義:

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

  public native int hashCode();

可以看到潜必,euqals方法是用this和參數(shù)對象obj用==做比較,在這里比較的是兩個對象的引用魂仍,如果兩個對象的引用相同拐辽,那么返回true,即兩個對象是相等的擦酌。hashCode()方法是一個本地方法俱诸,它是根據(jù)對象的內(nèi)存地址導(dǎo)出的一個hash值(對象的存儲地址)。
在實際應(yīng)用中赊舶,比較兩個對象的引用是否相同來確定兩個對象是否相等是沒有任何意義的睁搭,考慮如下代碼:

public class Person{
      private String name;
      private int age;
      public Person(String name, int age) {
        super();
        this.name = name;
        this.age = age;
    }
     // 省略get,set方法
}

public  class Test{
       
       public  static void main(String[] args){
             Person   sanmao = new Person("sanmao",15);
             Person  zhangsan = new Person("sanmao",15);
            System.out.println(sanmao.equals(zhangsan));
       }
    
}

上面我們定義了一個Person類,有兩個屬性笼平,name名稱和age年齡园骆。我們在main()方法中定義了兩個Person,他們的名字和年齡都是一樣的寓调,那么在實際抽象中我們認(rèn)為sanmao和zhangsan指的是同一個人锌唾,但實際結(jié)果上面的運(yùn)行結(jié)果卻輸出的是false。因為我們在Person類中沒有重寫繼承自O(shè)bject中的equals()方法夺英,實際比較的是兩個對象的引用是否相同晌涕,很明顯我們定義了兩個對象滋捶,當(dāng)然輸出的是false。為了實現(xiàn)我們想要的邏輯渐排,應(yīng)該修改Person類炬太,重寫equals()方法,定義自己的比較邏輯:

public class Person{
      private String name;
      private int age;
      public Person(String name, int age) {
        super();
        this.name = name;
        this.age = age;
    }
     // 省略get,set方法
      @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;
        return Objects.equals(name, other.name)
                && age==other.age;
    }
}

在這里為了防備name為null的情況我們使用Objects.equals方法驯耻,如果兩個參數(shù)都為null亲族,則該方法返回true,只有一個為null,返回false,兩個都不為null,則調(diào)用a.equals(b)返回實際值。

euqals方法必須遵守以下幾種特性:

  1. 自反性:對于任何非空引用x可缚,x.euqals(x)應(yīng)返回true霎迫。
  2. 對稱性:對于任何引用x和y,當(dāng)且僅當(dāng)下帘靡,x.equals(y)返回true時知给,y.equals(x)也應(yīng)該返回true。
  3. 傳遞性:對于任何引用x,y和z描姚,x.equals(y)返回true涩赢,y.equals(z)也返回true,那x.equals(z)應(yīng)該也返回true
  4. 一致性:如果x和y的引用沒有發(fā)生變化轩勘,反復(fù)調(diào)用x.equals(y)應(yīng)該返回相同的結(jié)果
  5. 對于任何非空引用x筒扒,x.equals(null)應(yīng)該返回false

現(xiàn)在我們運(yùn)行main方法可以看到輸出了true。

難道到這里就結(jié)束了嗎绊寻?NO... 我們經(jīng)常聽到重寫euqals方法就必須重寫hashCode方法花墩,它們兩者之間有什么聯(lián)系嗎?我們來修改一下main方法的代碼:

    public static void main(String[] args) {
        Set<Person> set=new HashSet<>();
        Person p1=new Person("sanmao",15);
        Person p2=new Person("sanmao",15);
        set.add(p1);
        set.add(p2);
        System.out.println(set.size());
    }

上面的代碼運(yùn)行結(jié)果為2澄步,這很奇怪冰蘑,Set中的元素是不會重復(fù)的,我們的p1和p2兩個對象一樣村缸,那我們期望的結(jié)果應(yīng)該是1,為什么會是2呢祠肥。我們來看一下HashSet的源碼:

 public class HashSet<E>  extends AbstractSet<E>  implements Set<E>, Cloneable, java.io.Serializable {
    static final long serialVersionUID = -5024744406713321676L;

   // Dummy value to associate with an Object in the backing Map
    private static final Object PRESENT = new Object();

  //省略超多個方法
    public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }
  //省略超多個方法
}

可以看到,HashSet內(nèi)部是用一個HashMap來做存儲的梯皿,實際調(diào)用的是HashMap的put方法搪柑,那我們繼續(xù)跟蹤一下HashMap的put方法:

    public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

可以看到,在put方法中調(diào)用了一個靜態(tài)的hash方法索烹,在該靜態(tài)方法中調(diào)用了該對象的hashCode方法。問題就在這里了弱睦,我們重寫了Person類的equals方法百姓,p1.equals(p2)返回了true,但是p1和p2的hashCode方法還是調(diào)用的繼承自O(shè)bject類的hashCode方法,即p1.hashCode()和p2.hashCode()返回的還是各自的存儲地址况木,在往map中插入元素的時候垒拢,由于hashCode返回值不相等旬迹,則直接往map中添加元素(這里設(shè)計到map添加元素的過程,這里就不詳細(xì)說了)求类,結(jié)果就導(dǎo)致邏輯上相同的連個對象被添加了兩次奔垦。
為了保證往Set中添加元素得到正確的結(jié)果,我們必須重寫hashCode方法:

public class Person{
      private String name;
      private int age;
      public Person(String name, int age) {
        super();
        this.name = name;
        this.age = age;
    }
     // 省略get,set方法
     @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;
        return Objects.equals(name, other.name)
                && age==other.age;
    }

      @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;
    }
}

現(xiàn)在Person類對象的hash值是由對象內(nèi)容導(dǎo)出的尸疆,能夠保證p1.equals(p2)==true時椿猎,p1.hashCode()==p2.hashCode。在和Set這樣的散列表一起工作時得到正確的結(jié)果寿弱。

在重寫euqals方法時犯眠,一定要重寫hashCode方法,確保散列表能夠正確結(jié)果症革。
如果兩個對象equals返回true,則hashCode也返回相同值筐咧。
如果兩個對象equals返回false,則hashCode不一定返回不同值。
如果兩個對象hashCode返回相同值噪矛,equals不一定返回true量蕊。
如果兩個對象hashCode返回不同值,equals一定返回false艇挨。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末残炮,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子雷袋,更是在濱河造成了極大的恐慌吉殃,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件楷怒,死亡現(xiàn)場離奇詭異蛋勺,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)鸠删,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進(jìn)店門抱完,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人刃泡,你說我怎么就攤上這事巧娱。” “怎么了烘贴?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵禁添,是天一觀的道長。 經(jīng)常有香客問我桨踪,道長老翘,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮铺峭,結(jié)果婚禮上墓怀,老公的妹妹穿的比我還像新娘。我一直安慰自己卫键,他們只是感情好傀履,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著莉炉,像睡著了一般钓账。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上呢袱,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天官扣,我揣著相機(jī)與錄音,去河邊找鬼羞福。 笑死惕蹄,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的治专。 我是一名探鬼主播卖陵,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼张峰!你這毒婦竟也來了泪蔫?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤喘批,失蹤者是張志新(化名)和其女友劉穎撩荣,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體饶深,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡餐曹,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了敌厘。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片台猴。...
    茶點(diǎn)故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖俱两,靈堂內(nèi)的尸體忽然破棺而出饱狂,到底是詐尸還是另有隱情,我是刑警寧澤宪彩,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布休讳,位于F島的核電站,受9級特大地震影響尿孔,放射性物質(zhì)發(fā)生泄漏衍腥。R本人自食惡果不足惜磺樱,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望婆咸。 院中可真熱鬧,春花似錦芜辕、人聲如沸尚骄。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽倔丈。三九已至,卻和暖如春状蜗,著一層夾襖步出監(jiān)牢的瞬間需五,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工轧坎, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留宏邮,地道東北人。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓缸血,卻偏偏與公主長得像蜜氨,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子捎泻,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評論 2 345

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