谈谈为什么重写equals()要重写hashcode()方法

谈谈为什么重写equals()要重写hashcode()方法

面试官可能会问你:“你重写过 hashCode()equals()么?为什么重写 equals() 时必须重写 hashCode() 方法?”

一个非常基础的问题,面试中的重中之重

开始的时候,我十分不理解,两个毫无相干的东西为什么要联系在一起?这篇文章就是研究过程,可能会借鉴一些网上的内容

首先来看

hashCode() 有什么用?

hashCode() 的作用是获取哈希码(int 整数),也称为散列码。这个哈希码的作用是确定该对象在哈希表中的索引位置。

hashCode()定义在 JDK 的 Object 类中,这就意味着 Java 中的任何类都包含有 hashCode() 函数。另外需要注意的是: ObjecthashCode() 方法是本地方法,也就是用 C 语言或 C++ 实现的,该方法通常用来将对象的内存地址转换为整数之后返回。

public native int hashCode();

重点是Objecthashcode()是为了将对象的内存地址转换为整数,如果不重写hashcode,即其他所有的对象基本默认都是继承这个方法。

回顾equals方法

回顾一下 Object的equals方法 实现,并简单汇总一下使用equals方法的规律。

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

通过上面Object的源代码,可以得出一个结论:如果一个类未重写equals方法,那么本质上通过“==”和equals方法比较的效果是一样的,都是比较两个对象的的内存地址。

前面两篇文章讲到StringInteger在比较时的区别,关键点也是它们对equals方法的实现。

面试时总结一下就是:默认情况下,从Object类继承的equals方法与“==”完全等价,比较的都是对象的内存地址。但我们可以重写equals方法,使其按照需要进行比较,如String类重写了equals方法,比较的是字符的序列,而不再是内存地址。

即这时,String类也肯定重写了hashcode()方法,因为ta重写了equals()

与hashCode方法的关系

那么equals方法与hashCode方法又有什么关系呢?我们来看Object上equals方法的一段注释。

Note that it is generally necessary to override the hashCode method whenever this method is overridden, so as to maintain the general contract for the hashCode method, which states that equal objects must have equal hash codes.

大致意思是:当重写equals方法后有必要将hashCode方法也重写,这样做才能保证不违背hashCode方法中“相同对象必须有相同哈希值”的约定。

此处只是提醒了我们重写hashCode方法的必要性,那其中提到的hashCode方法设计约定又是什么呢?相关的内容定义在hashCode方法的注解部分。

hashCode方法约定

关于hashCode方法的约定原文比较多,大家直接看源码即可看到,这里汇总一下,共三条:

(1)如果对象在使用equals方法中进行比较的参数没有修改,那么多次调用一个对象的hashCode()方法返回的哈希值应该是相同的。

(2)如果两个对象通过equals方法比较是相等的,那么要求这两个对象的hashCode方法返回的值也应该是相等的。

(3)如果两个对象通过equals方法比较是不同的,那么也不要求这两个对象的hashCode方法返回的值是不相同的。但是我们应该知道对于不同对象产生不同的哈希值对于哈希表(HashMap等)能够提高性能。

其实,看到这里我们了解了hashCode的实现规约,但还是不清楚为什么实现equals方法需要重写hashCode方法。但我们可以得出一条规律:

hashCode方法实际上必须要完成的一件事情就是,为equals方法认定为相同的对象返回相同的哈希值。

这里举一个实例

public void test1() {
    String s = "ok";
    StringBuilder sb = new StringBuilder(s);
    System.out.println(s.hashCode() + "  " + sb.hashCode());


    String t = new String("ok");
    StringBuilder tb = new StringBuilder(s);
    System.out.println(t.hashCode() + "  " + tb.hashCode());
}

上面这段代码打印的结果为:

3548  1833638914
3548  1620303253

String实现了hashCode方法,而StringBuilder并没有实现,这就导致即使值是一样的,hashCode也不同。

下面我们以HashMap为例,看看如果没有实现hashCode方法会导致什么严重的后果。

@Test
public void test2() {
    String hello = "hello";


    Map<String, String> map1 = new HashMap<>();
    String s1 = new String("key");
    String s2 = new String("key");
    map1.put(s1, hello);
    System.out.println("s1.equals(s2):" + s1.equals(s2));
    System.out.println("map1.get(s1):" + map1.get(s1));
    System.out.println("map1.get(s2):" + map1.get(s2));




    Map<Key, String> map2 = new HashMap<>();
    Key k1 = new Key("A");
    Key k2 = new Key("A");
    map2.put(k1, hello);
    System.out.println("k1.equals(k2):" + s1.equals(s2));
    System.out.println("map2.get(k1):" + map2.get(k1));
    System.out.println("map2.get(k2):" + map2.get(k2));
}


class Key {


    private String k;


    public Key(String key) {
        this.k = key;
    }


    @Override
    public boolean equals(Object obj) {
        if (obj instanceof Key) {
            Key key = (Key) obj;
            return k.equals(key.k);
        }
        return false;
    }
}

实例中定义了内部类Key,其中实现了equals方法,但未实现hashCode方法。存放于Map中的value值都是字符串“hello”。

代码分两段,第一段演示当Mapkey通过实现了hashCodeString时是什么效果;第二段演示了当Mapkey通过未实现hashCode方法的Key对象时是什么效果。

执行上述代码,打印结果如下:

s1.equals(s2):true
map1.get(s1):hello
map1.get(s2):hello
k1.equals(k2):true
map2.get(k1):hello
map2.get(k2):null

分析结果可以看出,对于String作为key的 s1 和 s2 来说,通过equals比较相等是自然的,获得的值也是相同的。但 k1 和 k2 通过equals比较是相等,但为什么在Map中获得的结果却不一样?本质上就是因为没有重写hashCode方法导致Map在存储和获取过程中调用hashCode方法获得的值不一致。

此时在Key类重写继承自Objecd的hashCode方法:

@Override
public int hashCode(){
    return k.hashCode();
}

再次执行,便可正常获得对应的值。

s1.equals(s2):true
map1.get(s1):hello
map1.get(s2):hello
k1.equals(k2):true
map2.get(k1):hello
map2.get(k2):hello

通过上面的典型实例演示了不重写hashCode方法的潜在后果。

即,如果不重写已经经过重写equals()的类的hashcode()方法,就不能保证当equals()返回结果是true,hashcode()也会返回相同值。

重写hashCode方法

了解了重写hashCode方法的重要性,也了解了对应的规约,那么下面我们就聊聊如何优雅的重写hashCode方法。

首先,如果使用IDEA的话,那么直接使用快捷键即可。
在这里插入图片描述

生成的效果如下:

@Override
public boolean equals(Object o) {
    if (this == o) {
        return true;
    }
    if (o == null || getClass() != o.getClass()) {
        return false;
    }
    Key key = (Key) o;
    return Objects.equals(k, key.k);
}
@Override
public int hashCode() {
    return Objects.hash(k);
}


   if (o == null || getClass() != o.getClass()) {
        return false;
    }
    Key key = (Key) o;
    return Objects.equals(k, key.k);
}


@Override
public int hashCode() {
    return Objects.hash(k);
}

引用材料来之https://www.w3cschool.cn/article/4adce89bf8b821.html

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值