重写equals为什么还要重写hashCode

什么是hashCode?

hashCode 也叫 哈希码,是由 32位二进制数 构成

Java 中不同类型对象产生hashCode原理

  • Integer 对象:hashCode == 其值
  • String 对象:hashCode == 字符串经过特殊运算产生的数(这里就不赘述了)
  • Object 对象:hashCode == 对象地址

对于 String 对象还可能产生 哈希冲突

原因:hashCode 是32位二进制数构成,也就是说,其范围是有限的,而字符串的范围是非常大的,远不够所有 hashCode 来表示。可得出以下结论

  • 字符串相同 hashCode 一定相同
  • 字符串不同 hashCode 不一定就不同(可能发生哈希冲突导致 哈希Code相同)
  • hashCode 不同,字符串内容一定不同

(hashCode作用) 哈希表存储数据原理

哈希表是一种数据结构:本质是 “链表”+“数组”,所以其包含了链表和数组的优点,是一种 查询效率高增删效率也高 的数据结构

Java 中的容器 HashSet容器 HashMap,使用的就是这样一种数据结构,由于 HashSet 底层使用的是 HashMap,所以下面以 HashMap 展开对哈希表的理解

HashMap:(存储 key-value 键值对)

底层使用 Node数组,在之前的版本中定义为 Entry数组,只是名称不同重写equals为什么还要重写hashCode_第1张图片Node 结构:
重写equals为什么还要重写hashCode_第2张图片
可见 Node是一个链表结构,包含 hash、key、value、next 四个部分,所以Node数组是一个链表+数组的结构,也就是 哈希表

  1. hash : 哈希值,hashCode经过运算得出的值,使其范围在[0,数组长度-1],表示元素需要存储在 哈希表(Node数组)的下标位置
    运算规则:
    重写equals为什么还要重写hashCode_第3张图片
    所以hashCode的作用是为了确定元素存放在哈希表的位置
    相同 hash 值的元素会以链表形式连接在同一数组下标
  2. key :键对象
  3. value :值对象
  4. next :下一个节点

哈希表可视化结构图:
重写equals为什么还要重写hashCode_第4张图片

存储规则:
重写equals为什么还要重写hashCode_第5张图片

  1. 传入参数 hash、key、value 等,为了简化理解我们只讨论这三个参数
  2. Node数组中寻找与我传入的 hash 相同的下标,判断其是否为空
  3. 为空说明该下标还没有元素,则直接将我传入的 key value 存入哈希表
  4. 不为空说明该下标已经有元素存在,需要将我要存入的元素链表形式连接在改下标的元素后,前提是 key 不同,不然已有的元素和需要存储的元素,有相同的hash和key,传入的新value就会覆盖原本的value
  5. 这样就可以保证哈希表中不会有两个相同的 key,因为 key 相同不会新增节点,而是覆盖该节点的value 值

所以哈希表中不会有相同的key存在

重写 equals 而不重写 hashCode 产生的影响

前面已经分析了哈希表的结构和存储原理,知道了哈希表不会有相同的key存在。那么请看下面这个例子

不重写 hashCode

public class TestEquHash {   // 测试类,重写了equals 没重写 hashCode
    private String s;
    public TestEquHash(String s) {this.s = s;}
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        TestEquHash that = (TestEquHash) o;
        return Objects.equals(s, that.s);
    }
}

结果

class Te {
    public static void main(String[] args) {
        TestEquHash te1 = new TestEquHash("abc");
        TestEquHash te2 = new TestEquHash("abc");
        System.out.println(te1.equals(te2));  // 重写了equals 比值 返回 true
        Map map = new HashMap();
        map.put(te1, 111);   // 由于没有重写 hashCode 方法 ,te1和te2 的 hashCode不同
        map.put(te2, 222);   // 两者存储在哈希表的不同下标位置,但他们的键值是相等的 都是 “abc” 与键值唯一冲突
        System.out.println(map.get(te1));   // 输出 111
        System.out.println(map.get(te2));   // 输出 222
    }
}

重写 hashCode

public class TestEquHash {   // 测试类,重写了equals 和 hashCode
    private String s;
    public TestEquHash(String s) {this.s = s;}
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        TestEquHash that = (TestEquHash) o;
        return Objects.equals(s, that.s);
    }
    @Override
    public int hashCode() {
        return Objects.hash(s);
    }
}

结果

class Te {
    public static void main(String[] args) {
        TestEquHash te1 = new TestEquHash("abc");
        TestEquHash te2 = new TestEquHash("abc");
        System.out.println(te1.equals(te2));  // 重写了equals 比值 返回 true
        Map map = new HashMap();
        map.put(te1, 111);   // 由于重写了 hashCode 方法 ,te1 te2 的 hashCode相同
        map.put(te2, 222);   // 两者会存储在哈希表同一下标位置,再加上它们的键值是相等的,都是 “abc”,所以value会被覆盖,保证了键值的唯一性
        System.out.println(map.get(te1));   // 输出 222
        System.out.println(map.get(te2));   // 输出 222
    }
}

所以说,重写 equals 方法还要重写 hashCode 方法的原因是为了:
防止键值相同的元素存储到哈希表,保证了哈希表的键值唯一

你可能感兴趣的:(java)