为什么重写了equals方法后还需要重写hashCode方法

分析

假如我们创建了一个 People 类,并且重写了其中的 equals 方法

public class People {

    private Integer age;
    private String name;

    // getter/setter方法
    ....

    @Override
    public boolean equals(Object obj) {
        return this.name.equals(((People) obj).name) && this.age == ((People) obj).age;
    }
    
}

我的想法是,在 HashMap 集合中放入 name 和 age 都相同的 People 对象,此时便认为是同一个人了,因为 HashMap 是无法存储重复的键的,因此我们看看输出是如何

@Test
public void test12() {
    HashMap<People, Integer> hashMap = new HashMap<>();

    People people = new People(10, "AAA");
    People people2 = new People(10, "AAA");

    hashMap.put(people, 2);
    hashMap.put(people2, 2);

    for (Map.Entry<People, Integer> entry : hashMap.entrySet()) {
        System.out.println(entry.getKey() + " " + entry.getValue());
    }
}

结果是:

People{age=10, name='AAA'} 2
People{age=10, name='AAA'} 2

可以看到,此时把两个 name 和 age 都相同的 People 对象打印出来了,此时明显不符合我的想法,因为这个时候 name 和 age 都相同,内容都相同,应该只会输出一个,那么怎么会输出 2 个“重复”的呢?看看 HashMap 的 put 方法的底层是如何实现的把

public V put(K key, V value) {
    if (table == EMPTY_TABLE) {
        inflateTable(threshold);
    }
    if (key == null)
        return putForNullKey(value);
    // 计算键的 hash 值
    int hash = hash(key);
    // 计算键在 Entry 数组中的位置 i
    int i = indexFor(hash, table.length);
    for (Entry<K,V> e = table[i]; e != null; e = e.next) {
        Object k;
        
        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
            V oldValue = e.value;
            e.value = value;
            e.recordAccess(this);
            return oldValue;
        }
    }

    modCount++;
    addEntry(hash, key, value, i);
    return null;
}

final int hash(Object k) {
    int h = hashSeed;
    if (0 != h && k instanceof String) {
        return sun.misc.Hashing.stringHash32((String) k);
    }

    // 此时如果键的hashCode()方法被重写了, 就调用重写后的hashCode()方法
    h ^= k.hashCode();
    
    h ^= (h >>> 20) ^ (h >>> 12);
    return h ^ (h >>> 7) ^ (h >>> 4);
}

我们通过调试,看看两次执行 put 方法之后的各个参数是怎样的吧,当第一次 put 的时候

第二次 put 的时候

可以看到,两次 put 的 hash 值并不相同,导致键在 Entry 数组中的位置 i 也不同,因为我们没有重写 People 类的 hashCode 方法,导致两次 put 进入的 people 对象计算出来的 hash 值不同,此时虽然我们传入的两个 People 对象的内容相同,但是因为 hash 值不同,导致 HashMap 以为我们传入的是 2 个不同的 People

如果我们要自定义当 People 类的 name 和 age 都相同时,便是同一个人,我们需要同时重写 People 类中的 equals 和 hashCode 方法

我们将 People 类中的 hashCode 方法也进行重写

public class People {

    private Integer age;
    private String name;

    public People(Integer age, String name) {
        this.age = age;
        this.name = name;
    }
    
    // getter/setter 方法
    ...
    
    @Override
    public boolean equals(Object obj) {
        return this.name.equals(((People) obj).name) && this.age == ((People) obj).age;
    }

    @Override
    public int hashCode() {
        return name.hashCode()*37+age;
    }
    
}

然后我们再次执行上面的那个测试类,结果是:

People{age=10, name='AAA'} 2

同样通过调试来查看结果,第一次 put 的时候

第二次 put 的时候

两次的 hash 是一样的,导致键在 Entry 数组中的位置也是一样的,此时该条件 e.hash == hash && ((k = e.key) == key || key.equals(k)) 便能够满足了,两次的 hash 值相同,同时调用 People 重写的 equals 方法后,也是一样的


结论

假如我们需要实现类似 People 这种基于对象内容来判断两个对象是否相等的情况时,我们肯定会在对象中重写 equals 方法,在平时的使用中,我们只要重写 equals 方法即可

扫描二维码关注公众号,回复: 5647638 查看本文章

但是如果涉及需要将对象放入类似 HashMap、HashSet 类似的集合中时,他们底层的原理都是,先判断传入的键的 hash 值是否相同,如果不同则直接放入集合中,如果相同,则在进行 equals 判断,如果 equals 也是相同,那么后来传入的键会将前面的键覆盖

对于 String、Integer 这类的包装类,底层已经重写了 hashCode 方法,即都是唯一的。但是,如果我们自己声明了类似 People 这样的对象,在没有重写 hashCode 方法的情况下,在将对象传入集合类的过程中,会首先计算你传入值的 hash 值,因为对象没有重写 hashCode 方法,因此你两次放入的内容相同的对象还是会被当作两个不同的对象。此时,唯一的解决方法便是,在对象中重写 hashCode 方法


参考

https://www.cnblogs.com/dolphin0520/p/3681042.html
https://mp.weixin.qq.com/s/aDDotZphhDRCWV4nAZbwhQ

猜你喜欢

转载自blog.csdn.net/babycan5/article/details/88586821