爲什(shén)麽重寫了(le)equals方法,就必須重寫hashCode

發布時(shí)間:2022-02-23 10:41:40 作者:King 來(lái)源:本站 浏覽量(2555) 點贊(98)
摘要:先來(lái)看阿裏巴巴Java開發手冊中的(de)一段話(huà):【強制】關于 hashCode 和(hé) equals 的(de)處理(lǐ),遵循如下(xià)規則:1) 隻要重寫 equals,就必須重寫 hashCode。2) 因爲 Set 存儲的(de)是不重複的(de)對(duì)象,依據 hashCode 和(hé) equals 進行判斷,所以 Set 存儲的(de) 對(duì)象必須重寫這(zhè)兩個(gè)方法。3) 如果自定義對(duì)象作爲 Map 的(de)鍵,那麽必須重寫 hashCode 和(hé)

先來(lái)看阿裏巴巴Java開發手冊中的(de)一段話(huà):

【強制】關于 hashCode 和(hé) equals 的(de)處理(lǐ),遵循如下(xià)規則:1) 隻要重寫 equals,就必須重寫 hashCode。2) 因爲 Set 存儲的(de)是不重複的(de)對(duì)象,依據 hashCode 和(hé) equals 進行判斷,所以 Set 存儲的(de) 對(duì)象必須重寫這(zhè)兩個(gè)方法。3) 如果自定義對(duì)象作爲 Map 的(de)鍵,那麽必須重寫 hashCode 和(hé) equals。說明(míng):String 重寫了(le) hashCode 和(hé) equals 方法,所以我們可(kě)以非常愉快(kuài)地使用(yòng) String 對(duì)象 作爲 key 來(lái)使用(yòng)。

它要求我們若是重寫equals方法則必須強制重寫hashCode,這(zhè)是爲何呢(ne)?

1equals和(hé)hashCode方法

我們先來(lái)了(le)解一下(xià)這(zhè)兩個(gè)方法,它們都來(lái)自Object類,說明(míng)每一個(gè)類中都會有這(zhè)麽兩個(gè)方法,那它倆的(de)作用(yòng)是什(shén)麽呢(ne)?

首先是equals方法,它是用(yòng)來(lái)比較兩個(gè)對(duì)象是否相等。對(duì)于equals方法的(de)使用(yòng),得(de)分(fēn)情況討(tǎo)論,若是子類重寫了(le)equals方法,則将按重寫的(de)規則進行比較,比如:

public static void main(String[] args) {
    String s = "hello";
    String str2 = "world";
    boolean result = s.equals(str2);
    System.out.println(result);
}

來(lái)看看String類對(duì)equals方法的(de)重寫:

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

由此可(kě)知,String類調用(yòng)equals方法比較的(de)将是字符串的(de)内容是否相等。又如:

public static void main(String[] args) {
    Integer a = 500;
    Integer b = 600;
    boolean result = a.equals(b);
    System.out.println(result);
}

觀察Integer類的(de)實現:

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

它比較的(de)仍然是值,然而若是沒有重寫equals方法:

@AllArgsConstructor
static class User {
    private String name;
    private Integer age;
}

public static void main(String[] args) {
    User user = new User("zs"20);
    User user2 = new User("zs"20);
    boolean result = user.equals(user2);
    System.out.println(result);
}

即使兩個(gè)對(duì)象中的(de)值是一樣的(de),它也(yě)是不相等的(de),因爲它執行的(de)是Object類的(de)equals方法:

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

我們知道,對(duì)于引用(yòng)類型,==比較的(de)是兩個(gè)對(duì)象的(de)地址值,所以結果爲false,若是想讓兩個(gè)内容相同的(de)對(duì)象在equals後得(de)到true,則需重寫equals方法:

@AllArgsConstructor
static class User {
    private String name;
    private Integer age;

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        User user = (User) o;
        return Objects.equals(name, user.name) && Objects.equals(age, user.age);
    }
}

再來(lái)聊一聊hashCode方法,它是一個(gè)本地方法,用(yòng)來(lái)返回對(duì)象的(de)hash碼值,通(tōng)常情況下(xià),我們都不會使用(yòng)到這(zhè)個(gè)方法,隻有Object類的(de)toString方法使用(yòng)到了(le)它:

public String toString() {
    return getClass().getName() + "@" + Integer.toHexString(hashCode());
}

2爲什(shén)麽隻要重寫了(le)equals方法,就必須重寫hashCode

了(le)解兩個(gè)方法的(de)作用(yòng)後,我們來(lái)解決本篇文章(zhāng)的(de)要點,爲什(shén)麽隻要重寫了(le)equals方法,就必須重寫hashCode呢(ne)?這(zhè)是針對(duì)一些使用(yòng)到了(le)hashCode方法的(de)集合而言的(de),比如HashMap、HashSet等,先來(lái)看一個(gè)現象:

public static void main(String[] args) {
    Map<Object, Object> map = new HashMap<>();
    String s1 = new String("key");
    String s2 = new String("key");

    map.put(s1, 1);
    map.put(s2, 2);
    map.forEach((k, v) -> {
        System.out.println(k + "--" + v);
    });
}

這(zhè)段程序的(de)輸出結果是:key--2,原因是HashMap中的(de)key不能重複,當有重複時(shí),後面的(de)數據會覆蓋原值,所以HashMap中隻有一個(gè)數據,那再來(lái)看下(xià)面一段程序:

@AllArgsConstructor
@ToString
static class User {
    private String name;
    private Integer age;
}

public static void main(String[] args) {
    Map<Object, Object> map = new HashMap<>();
    User user = new User("zs"20);
    User user2 = new User("zs"20);

    map.put(user, 1);
    map.put(user2, 2);
    map.forEach((k, v) -> {
        System.out.println(k + "--" + v);
    });
}

它的(de)結果應該是什(shén)麽呢(ne)?是不是和(hé)剛才一樣,HashMap中也(yě)隻有一條數據呢(ne)?可(kě)運行結果卻是這(zhè)樣的(de):

EqualsAndHashCodeTest.User(name=zs, age=20)--1
EqualsAndHashCodeTest.User(name=zs, age=20)--2

這(zhè)是爲什(shén)麽呢(ne)?這(zhè)是因爲HashMap認爲這(zhè)兩個(gè)對(duì)象并不相同,那我們就重寫equals方法:

@AllArgsConstructor
@ToString
static class User {
    private String name;
    private Integer age;

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        User user = (User) o;
        return Objects.equals(name, user.name) && Objects.equals(age, user.age);
    }
}

public static void main(String[] args) {
    Map<Object, Object> map = new HashMap<>();
    User user = new User("zs"20);
    User user2 = new User("zs"20);

    System.out.println(user.equals(user2));

    map.put(user, 1);
    map.put(user2, 2);
    map.forEach((k, v) -> {
        System.out.println(k + "--" + v);
    });
}

運行結果:

true
EqualsAndHashCodeTest.User(name=zs, age=20)--1
EqualsAndHashCodeTest.User(name=zs, age=20)--2

兩個(gè)對(duì)象判斷是相同的(de),但HashMap中仍然存放了(le)兩條數據,說明(míng)HashMap仍然認爲這(zhè)是兩個(gè)不同的(de)對(duì)象。這(zhè)其實涉及到HashMap底層的(de)原理(lǐ),查看HashMap的(de)put方法:

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

在存入數據之前,HashMap先對(duì)key調用(yòng)了(le)hash方法:

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

該方法會調用(yòng)key的(de)hashCode方法并做(zuò)右移、異或等操作,得(de)到key的(de)hash值,并使用(yòng)該hash值計算(suàn)得(de)到數據的(de)插入位置,如果當前位置沒有元素,則直接插入,如下(xià)圖所示:

爲什(shén)麽重寫了(le)equals方法,就必須重寫hashCode

既然兩個(gè)對(duì)象求得(de)的(de)hash值不一樣,那麽就會得(de)到不同的(de)插入位置,由此導緻HashMap最終存入了(le)兩條數據。

接下(xià)來(lái)我們重寫User對(duì)象的(de)hashCode和(hé)equals方法:

@AllArgsConstructor
@ToString
static class User {
    private String name;
    private Integer age;

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        User user = (User) o;
        return Objects.equals(name, user.name) && Objects.equals(age, user.age);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}

那麽此時(shí)兩個(gè)對(duì)象計算(suàn)得(de)到的(de)hash值就會相同:當通(tōng)過hash計算(suàn)得(de)到相同的(de)插入位置後,user2便會發現原位置上已經有數據了(le),此時(shí)将觸發equals方法,對(duì)兩個(gè)對(duì)象的(de)内容進行比較,若相同,則認爲是同一個(gè)對(duì)象,再用(yòng)新值覆蓋舊(jiù)值,所以,我們也(yě)必須重寫equals方法,否則,HashMap始終會認爲兩個(gè)new 出來(lái)的(de)對(duì)象是不相同的(de),因爲它倆的(de)地址值不可(kě)能一樣。

由于String類重寫了(le)hashCode和(hé)equals方法,所以,我們可(kě)以放心大(dà)膽地使用(yòng)String類型作爲HashMap的(de)key。

在HashSet中,同樣會出現類似的(de)問題:

@AllArgsConstructor
@ToString
static class User {
    private String name;
    private Integer age;
}

public static void main(String[] args) {
    Set<Object> set = new HashSet<>();
    User user = new User("zs"20);
    User user2 = new User("zs"20);

    set.add(user);
    set.add(user2);

    set.forEach(System.out::println);
}

對(duì)于内容相同的(de)兩個(gè)對(duì)象,若是沒有重寫hashCode和(hé)equals方法,則HashSet并不會認爲它倆重複,所以會将這(zhè)兩個(gè)User對(duì)象都存進去。

3總結

hashCode的(de)本質是幫助HashMap和(hé)HashSet集合加快(kuài)插入的(de)效率,當插入一個(gè)數據時(shí),通(tōng)過hashCode能夠快(kuài)速地計算(suàn)插入位置,就不需要從頭到尾地使用(yòng)equlas方法進行比較,但爲了(le)不産生問題,我們需要遵循以下(xià)的(de)規則:

  • 兩個(gè)相同的(de)對(duì)象,其hashCode值一定相同
  • 若兩個(gè)對(duì)象的(de)hashCode值相同,它們也(yě)不一定相同

所以,如果不重寫hashCode方法,則會發生兩個(gè)相同的(de)對(duì)象出現在HashSet集合中,兩個(gè)相同的(de)key出現在Map中,這(zhè)是不被允許的(de),綜上所述,在日常的(de)開發中,隻要重寫了(le)equals方法,就必須重寫hashCode。


微信

掃一掃,關注我們

感興趣嗎?

歡迎聯系我們,我們願意爲您解答(dá)任何有關網站疑難問題!

【如有開發需求】那就聯系我們吧

搜索千萬次不如咨詢1次

承接:網站建設,手機網站,響應式網站,小程序開發,原生android開發等業務

立即咨詢 16605125102