为什么重写了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 方法即可

但是如果涉及需要将对象放入类似 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

  • 7
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值