在Java面向对象编程中,对象的相等性比较有两个关键的方案:
public boolean equals(Object obj){
return (this == obj)
}
1.2 hashCode方法:散列世界的身份证
public native int hashCode();
Java语言规范明确规定:
如果两个对象通过equals()方法比较相等,那么他们的hashCode()必须返回相同的整数值。
这条规则构成了Java对象模型的基石,其必要性体现在:
HashMap、HashSet等基于哈希表的集合类
存储时:先计算hashCode确定存储位置
查找时:先比较hashCode,再使用equals验证
防止出现"逻辑相等但哈希不同"的对象
避免在集合中出现重复元素
确保对象作为Map键时的正确行为
class Student {
String id;
@Override
public boolean equals(Object o) {
return ((Student)o).id.equals(this.id);
}
}
Set set = new HashSet<>();
set.add(new Student("1001"));
System.out.println(set.contains(new Student("1001"))); // 可能返回false
结果分析:
新创建的Student对象虽然内容相同
但由于未重写hashCode,哈希值不同
HashSet在不同哈希桶中查找,永远找不到目标对象
Map map = new HashMap<>();
Student s1 = new Student("1001");
map.put(s1, "Alice");
Student s2 = new Student("1001");
System.out.println(map.get(s2)); // 可能返回null
问题根源:
s1和s2内容相等但哈希不同
HashMap在不同的桶中查找键值
即使equals返回true也无法正确检索
自反性:x.equals(x)必须返回true
对称性:x.equals(y)与y.equals(x)结果一致
传递性:x.equals(y)且y.equals(z)则x.equals(z)
一致性:多次调用结果相同(前提未修改)
非空性:x.equals(null)必须返回false
一致性:对象状态未改变时返回值稳定
相等性:equals为true时hashCode必须相同
离散性:不相等的对象尽量产生不同哈希值
使用Java标准库:
@Override
public int hashCode() {
return Objects.hash(id, name, age);
}
Apache Commons实现:
@Override
public int hashCode() {
return new HashCodeBuilder(17, 37)
.append(id)
.append(name)
.append(age)
.toHashCode();
}
IntelliJ IDEA自动生成:
@Override
public int hashCode() {
int result = id != null ? id.hashCode() : 0;
result = 31 * result + (name != null ? name.hashCode() : 0);
result = 31 * result + age;
return result;
}
31 = 2⁵ - 1,便于位运算优化(31 * i = (i << 5) - i)
中等大小的质数,平衡碰撞概率与计算效率
在英文字符组合中表现出良好的分布特性
完美哈希:理论存在但实现成本高
负载因子:HashMap默认0.75的权衡
再哈希策略:开放地址法 vs 链地址法
一旦创建不可修改
可缓存hashCode值提升性能
private int hash; // 默认0
@Override
public int hashCode() {
if (hash == 0) {
hash = Objects.hash(id, name);
}
return hash;
}
父类已重写equals/hashCode
子类新增字段需重新实现
@Override
public int hashCode() {
return super.hashCode() * 31 + Objects.hash(newField);
}
Hibernate/JPA实体类
Lombok的@EqualsAndHashCode
Jackson序列化/反序列化
@Test
public void testEqualsContract() {
Student s1 = new Student("1001");
Student s2 = new Student("1001");
assertTrue(s1.equals(s2));
assertEquals(s1.hashCode(), s2.hashCode());
}
在Java中,equals()
和hashCode()
方法都是从Object
类继承而来的方法。当你重写equals()
方法时,通常也需要重写hashCode()
方法,原因主要与Java集合框架(特别是哈希表实现如HashMap
, HashSet
等)的工作原理有关。
哈希表是一种数据结构,它通过将对象的哈希码映射到表中的一个位置来存储对象。这个过程依赖于对象的hashCode()
方法生成一个整数(哈希码),然后根据这个哈希码确定对象在哈希表中的存储位置。当需要查找对象时,首先计算其哈希码以快速定位可能的位置,然后使用equals()
方法检查该位置上的对象是否确实是我们要找的对象。
为了保证哈希表等集合类型的正确性,equals()
和hashCode()
方法之间需要保持一定的合同关系:
如果两个对象相等(即a.equals(b)
为true
),那么它们必须具有相同的哈希码(即a.hashCode() == b.hashCode()
)。
如果两个对象的哈希码相同,则这两个对象不一定相等。 这是因为不同的对象可能会巧合地拥有相同的哈希码,这被称为哈希碰撞。
如果不遵守这些规则,可能会导致如下问题:
检索失败: 如果你修改了equals()
方法而不相应地修改hashCode()
,那么即使两个对象被认为是“相等”的(依据你的新equals()
定义),它们也可能因为有不同的哈希码而被存放在哈希表的不同位置,从而导致无法正确检索。
逻辑错误: 在某些情况下,这样的不一致还可能导致更严重的逻辑错误或程序崩溃,尤其是在高度依赖于对象身份和相等性的复杂应用程序中。
因此,当你重写equals()
方法时,确保也重写了hashCode()
方法,并且遵循上述的一致性原则是非常重要的。这样做可以确保基于哈希的集合能够正确地工作,并且能够高效地存储和检索对象。