0
点赞
收藏
分享

微信扫一扫

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

北邮郭大宝 2022-03-13 阅读 111

谈谈为什么重写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方法的一段注释。

大致意思是:当重写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 条评论