注:本专栏文章均为本人原创,未经本人授权请勿私自转载,谢谢。
哈希码的定义
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 表时,应遵循以下规约:
- Hash 表中键的最佳选择还是 String 类,若要使用自定义类,请保证所有用于计算 hashCode 的字段都是 final 的。
- 若必须变更会导致 hashCode 更改的非 final 字段,应先从 hash 表中删除该对象,在修改后再放入 hash 表。
- 为了让同一个对象(equals相等)具有相同的 hashCode,则计算 hashCode 的字段必须为计算 equals 的字段的子集;