解锁Python编程的无限可能:《奇妙的Python》带你漫游代码世界
每当你在类中重写 equals
方法时,必须同时重写 hashCode
。如果不这样做,你的类将违反 hashCode
的通用合同,导致在使用 HashMap
、HashSet
等基于哈希的集合时出现问题。hashCode
方法合同的主要要求如下:
- 在应用程序执行期间,每次调用对象的
hashCode
方法时,如果对象未发生改变,应返回相同的值。该值不必在不同应用程序执行之间保持一致。 - 如果两个对象根据
equals
方法相等,则它们的hashCode
必须相同。 - 如果两个对象根据
equals
方法不相等,它们的hashCode
不必不同,但生成不同hashCode
值可以提升哈希表的性能。
为什么需要重写 hashCode
假设我们有一个 PhoneNumber
类(如【条目10】所示)用作 HashMap
的键。如果该类只重写了 equals
,而没有重写 hashCode
,则即使两个 PhoneNumber
实例在逻辑上相等,它们的哈希值也会不同,这违反了 hashCode
合同。由于哈希值不同,HashMap
在尝试检索时可能会在错误的哈希桶中查找,导致 get
方法返回 null
,即便插入时相等的实例的键值对确实存在。
为了解决这个问题,我们需要为 PhoneNumber
类编写一个合适的 hashCode
方法。以下是一个简单的例子:
// 合适的 hashCode 方法
@Override public int hashCode() {
int result = Short.hashCode(areaCode);
result = 31 * result + Short.hashCode(prefix);
result = 31 * result + Short.hashCode(lineNum);
return result;
}
编写一个好的 hashCode
方法
要编写一个好的 hashCode
方法,可以遵循以下步骤:
- 声明一个
int
类型的变量result
,并将第一个重要字段的hashCode
赋值给它。 - 对于每个剩余的字段,计算其
hashCode
并将其加入result
中:result = 31 * result + c;
,其中c
是该字段的hashCode
。 - 返回
result
。
通过上述方法,hashCode
方法将确保逻辑上相等的对象有相同的哈希值,从而符合 hashCode
合同。
额外注意事项
-
缓存
hashCode
:如果计算hashCode
的开销较大且类是不可变的,可以考虑在对象实例化时缓存hashCode
,以避免每次调用时重新计算。不过,需要确保在并发环境下缓存是线程安全的。 -
不要排除重要字段:为了提升性能,不要排除在
equals
方法中被比较的字段,否则可能导致多个实例被映射到相同的哈希码,从而影响哈希表的性能。 -
使用现有的工具:Java 提供了一些工具类,例如
Objects.hash()
,可以简化hashCode
方法的编写,虽然这种方法的性能较差,尤其是涉及原始类型时需要进行装箱和拆箱操作。在性能不敏感的场景中可以使用这种简化的方式。
性能考虑
hashCode
方法的质量直接影响哈希表的性能。如果不同实例的 hashCode
过于相似,哈希表的性能会退化成链表的线性查找,严重影响效率。因此,虽然编写一个 hashCode
方法可能略显繁琐,但它对于保证哈希表的性能至关重要。
总结
每次重写 equals
方法时,必须同时重写 hashCode
,否则程序中的基于哈希的集合将无法正常工作。hashCode
方法应遵循合同规定,确保逻辑上不相等的对象尽量产生不同的哈希值。遵循本文中提供的步骤和建议,可以确保你编写的 hashCode
方法高效且可靠。