Java中的hashCode()方法

67 阅读2分钟

注:本专栏文章均为本人原创,未经本人授权请勿私自转载,谢谢。

哈希码的定义

HashCode() 的作用就是获取哈希码,也称为散列码;它实际上是返回一个 int 整数。在 HashMap 中可以通过该值确定对象在散列表中的位置。哈希码具有如下特点:

  • 如果两个对象相等,则 equals() 和 hashCode() 都是相同的。
  • 如果两个对象的 hashCode() 值相同,那它们也不一定相等。

为什么重写equals()必须重写hashCode()

类中默认的 equals() 方法实现为比较两者引用是否相等,默认的 hashCode() 方法实现也可以理解为同一个类的每个对象实例具有不同的哈希值。

若某类只重写了equals() 方法,则同一个类的两个对象的 equals() 方法可能返回 true,但由于未重写 hashCode() 方法,这两个对象的哈希值仍不同,这违反了 hashCode 的定义。

同时,在这种情况下,这两个实例可能被存到 HashMap 中不同的桶里,从而导致不同的桶里有两个相同的实例,这与哈希表的理念相悖,也违反了哈希值的特性。

重写 hashCode() 方法可能导致的内存泄漏

一般的 hashCode() 实现都是将类内字段值进行加权计算得出,若这些字段不是 final 修饰的,那么更改这些字段的值就可以更改当前对象的哈希值。考虑以下情况:

存在一个以该类为 key 的哈希表,将<对象A, "abc">放入该哈希表,其哈希值索引到桶 1 位置处。

修改对象 A 的字段,使其哈希值发生改变,此时对象A的哈希值索引到哈希表的桶 2 位置处。

使用对象 A 作为 key 并未在桶 2 处查找到所对应的值,返回值为 null,原先桶 1 位置处发生内存泄漏。

static class Cat {
    String color;
    String name;
    int age;
​
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Cat cat = (Cat) o;
        return age == cat.age && Objects.equals(color, cat.color) && Objects.equals(name, cat.name);
    }
​
    @Override
    public int hashCode() {
        return Objects.hash(color, name, age);
    }
}
​
@Test
public void test_hash() {
    Map<Cat, String> map = new HashMap<>();
    Cat cat = new Cat();
    cat.color = "red";
    cat.name = "alice";
    cat.age = 3;
    map.put(cat, "abc");
    System.out.println(map.get(cat));
    cat.name = "alice1";
    System.out.println(map.get(cat));
}

输出结果如下:

abc
null

对于上述情况,在 hash 表中产生了无法被访问的实例,从而导致了内存泄漏。所以,平时在使用 hash 表时,应遵循以下规约:

  1. Hash 表中键的最佳选择还是 String 类,若要使用自定义类,请保证所有用于计算 hashCode 的字段都是 final 的。
  2. 若必须变更会导致 hashCode 更改的非 final 字段,应先从 hash 表中删除该对象,在修改后再放入 hash 表。
  3. 为了让同一个对象(equals相等)具有相同的 hashCode,则计算 hashCode 的字段必须为计算 equals 的字段的子集;