爲什麽重寫了equals方法,就必須重寫hashCode

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

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

【強制】關于 hashCode 和 equals 的處理,遵循如下規則:1) 隻要重寫 equals,就必須重寫 hashCode。2) 因爲 Set 存儲的是(shì)不重複的對象,依據 hashCode 和 equals 進行判斷,所以 Set 存儲的 對象必須重寫這兩個方法。3) 如果自定義對象作爲 Map 的鍵,那麽必須重寫 hashCode 和 equals。說明:String 重寫了 hashCode 和 equals 方法,所以我們可以非常愉快地使用 String 對象 作爲 key 來使用。

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

1equals和hashCode方法

我們先來了解一下這兩個方法,它們都來自Object類,說明每一個類中都會有這麽兩個方法,那它倆的作用是(shì)什麽呢?

首先是(shì)equals方法,它是(shì)用來比較兩個對象是(shì)否相(xiàng)等。對于equals方法的使用,得分情況讨論,若是(shì)子類重寫了equals方法,則将按重寫的規則進行比較,比如:

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

來看看String類對equals方法的重寫:

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

由此可知(zhī),String類調用equals方法比較的将是(shì)字符串的内容是(shì)否相(xiàng)等。又(yòu)如:

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

觀察Integer類的實現(xiàn):

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

它比較的仍然是(shì)值,然而若是(shì)沒有重寫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);
}

即使兩個對象中的值是(shì)一樣的,它也是(shì)不相(xiàng)等的,因爲它執行的是(shì)Object類的equals方法:

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

我們知(zhī)道,對于引用類型,==比較的是(shì)兩個對象的地址值,所以結果爲false,若是(shì)想讓兩個内容相(xiàng)同的對象在equals後得到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);
    }
}

再來聊一聊hashCode方法,它是(shì)一個本地方法,用來返回對象的hash碼值,通常情況下,我們都不會使用到這個方法,隻有Object類的toString方法使用到了它:

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

2爲什麽隻要重寫了equals方法,就必須重寫hashCode

了解兩個方法的作用後,我們來解決本篇文章的要點,爲什麽隻要重寫了equals方法,就必須重寫hashCode呢?這是(shì)針對一些使用到了hashCode方法的集合而言的,比如HashMap、HashSet等,先來看一個現(xiàn)象:

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

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

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

它的結果應該是(shì)什麽呢?是(shì)不是(shì)和剛才一樣,HashMap中也隻有一條數據呢?可運行結果卻是(shì)這樣的:

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

這是(shì)爲什麽呢?這是(shì)因爲HashMap認爲這兩個對象并不相(xiàng)同,那我們就重寫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

兩個對象判斷是(shì)相(xiàng)同的,但(dàn)HashMap中仍然存放(fàng)了兩條數據,說明HashMap仍然認爲這是(shì)兩個不同的對象。這其實涉及到HashMap底層的原理,查看HashMap的put方法:

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

在存入數據之前,HashMap先對key調用了hash方法:

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

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

爲什麽重寫了equals方法,就必須重寫hashCode

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

接下來我們重寫User對象的hashCode和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);
    }
}

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

由于String類重寫了hashCode和equals方法,所以,我們可以放(fàng)心大膽地使用String類型作爲HashMap的key。

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

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

對于内容相(xiàng)同的兩個對象,若是(shì)沒有重寫hashCode和equals方法,則HashSet并不會認爲它倆重複,所以會将這兩個User對象都存進去(qù)。

3總結

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

  • 兩個相(xiàng)同的對象,其hashCode值一定相(xiàng)同
  • 若兩個對象的hashCode值相(xiàng)同,它們也不一定相(xiàng)同

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


微信

掃一掃,關注我們

感興趣嗎(ma)?

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

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

搜索千萬次不如咨詢1次

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

立即咨詢 16605125102