HashCode 和 equals 学习笔记

本文探讨equals与hashCode在Java中的协作作用,解释为何重写equals需重写hashCode,以及如何影响Map和Set的性能。重点剖析未重写hashCode的后果,以及哈希冲突的处理策略。
摘要由CSDN通过智能技术生成

HashCode 和 equals

hashCode 和 equals 用来标识对象,两个方法协同工作可以用来判断两个对象是否相等。根据生成的哈希码将数据离散开来,可以使存取数据更快。对象通过调用 Object.equals() 生成哈希值;由于不可避免地会存在哈希值冲突的情况,因此当 hashCode 相同时,还需要再调用 equal 进行一次值的比较;但是 hashCode 不同,直接判定 Objects 不同,跳过 equals ,这加快了冲突处理效率。 Object 类定义中对 hashCode 和 equals 要求如下:

  1. 如果两个对象的 equals 的结果是相等的,则两个对象的 hashCode 的返回结果也必须是相同的。

  2. 任何时候重写 equals ,都必须同时重写 hashCode。

在 Map 和 Set 类集合中,用到这两个方法时,首先判断 hashCode 的值,如果 hash 相等,则再判断 equals 的结果,HashMap 的 get 判断代码如下:

public V get(Object key) {
    Node<K,V> e;
    return (e = getNode(hash(key), key)) == null ? null : e.value;
}

//	而其中 getNode 方法中有一个判断如下

if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
   		return e;

if 条件表达式中的 e.hash == hash 是先觉条件,只有相等才会执行后面的判断逻辑。如果不相等,后面的判断逻辑也就不会执行。equals 不相等时并不强制要求 hashCode 也不相等,但是哈希算法的初衷是尽可能让元素均匀分布,降低哈希冲突的概率,即在 equals 不相等时尽量使 hashCode 也不相等,这样 && 或 || 短路操作一旦生效,会极大地提高程序的执行效率。如果自定义对象作为 Map 的键,那么必须重写 hashCode 和 equals 。此外,因为 Set 存储的是也是不重复的对象,依据 hashCode 和 equals 进行判断,所以 Set 存储的自定义对象也必须重写这两个方法。而具体重写了 equals方法,但是没有重写 hashCode 方法,会有什么影响。下面通过实例来讲解下

public class EqualsObject {

    private int id;
    private String name;

    public EqualsObject(int id, String name) {
        this.id = id;
        this.name = name;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;	//	1
        if (o == null || getClass() != o.getClass()) return false;	// 2
        EqualsObject that = (EqualsObject) o;
        return id == that.id && Objects.equals(name, that.name);
    }

}

1 处先判断是否是统一对象,是的话直接返回 true

2 处说明,首先判断两个对象的类型是否相同,如果不匹配,则直接返回 false。另外此处使用了 getClass 的方式,就是严格限制了只有 Eqauls 对象本身才可以执行 equals 操作。

注意,上例代码中并没有重写 hashCode 方法,然后我们将上例对象加入到 HashSet 中测试下:

在这里插入图片描述
在这里插入图片描述

最终输出的结果是3。但是这些 EqualsObject 对象明显是相同的,但是在 HashSet 中,应该只会存在一个。之所以结果为 3 ,是因为如果不重写 hashCode() ,即使 equals() 相等也毫无意义,Object.hashCode 的实现是默认为每一个对象生成一个不同的 int 数值,他本身是 native 方法,一般与对象的内存地址有关。

关于重写equals尽量重写hashcode方法

在没有重写 equals 方法时,我们直接调用的是 Object类里面的equals方法,这个方法比较的是两个对象的内存地址,如果通过new 关键字新建的两个对象,

User user1 = new User("小明", "20", "湖南")User user2 = new User("小明", "20", "湖南")

对应的属性都完全相同,但是它们分配的内存地址是不一样的,也就是属于不同的对象,因此调用这个方法时就是为false,但是明显不符合的我们想要的结果,在我们看来,这两个对象就是同一个对象,它们应该返回的是true。因此,想要让这两个对象相等,我们就需要重写 equals 方法。但是 hashCode 方法是根据内存来计算的,在保证这两个小明都是同一个人(同一对象)的前提下,它们对应的hashCode理应也应该是一样的,但是通过new关键字新建的对象对应的hashCode也明显不一样,这时候也需要我们重写hashCode方法,人为地返回true。

例如用到map的时候,map是根据key的hashCode和key.equals是否都相等判断是否存在重复的entry的。所以如果我们从map中put进两个值(key都为 new User(“小明”, “20”, “湖南”) ),而没有重写hashCode,得到的结果(size = 2)和我们期望的结果(size=1)将不一样

为什么Hashmap键值可以为null,而HashTable和ConcurrentHashMap不行?如果并发容器键值对可以为null 在多线程并发下哪个场景会有问题?

在hashMap中,当存入一个key为username,值为null的数据时,调用了map.get(“username”),get() 方法的返回值时null。假设当前我们并不知道hashMap是否存在key为username的键值对,这时候我们需要有个方法来判断这个null到底是我们存进去的,还是这个key不存在而返回的null。在单线程的情况下下,我们通过contains方法来验证下

if (map.contains("username")){
    return map.get("username");
}

但是在ConcurrentHashMap或者HashTable中,如果允许键值都能为null,并且在多线程的情况下,当一个线程执行完 map.contains("username")时,其他一个线程已经把这个username的键值对给删掉了,这个时候我们通过get("username")得到其实是键值对不存在的null,而非我们存进去的key为username,值为null的null。

如果键值不允许为null?假设当前有个键为username,值为xiaoming,执行到map.contains(“username”)时返回true,此时第二个线程进来把该键值对删除了,map.get(“username”)就会返回为null,而此时这个null就能很明确的表明该键不存在,而不是模棱两可(1.这个值就是null2.这个键不存在)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值