浅谈 equals() 和 hashCode()
Object 类中定义了
equals()
和hashCode()
,在 Java 开发中大量涉及这两个方法,特别是集合框架。
方法作用:
- hashCode():用于计算对象哈希值。
- equals():用于比较对象是否相等。
1、方法实现
1.1、默认实现
基于对象引用地址
-
equals():比较两个对象引用是否指向同一内存地址。
-
hashCode():返回对象内存地址(本地方法)。
public boolean equals(Object obj) { return (this == obj); } public native int hashCode();
1.2、方法重写
为什么要重写?
- Object 默认实现:只有同一个对象引用的
equals()
和hashCode()
才会相等。 - 在实际业务中,需要为不同对象成对重写方法,才能自定义比较规则。
不重写的后果?
以 HashMap 为例:由于对象引用不同,即使属性完全相同也无法获取到集合中的元素。
HashMap<User> userMap = new HashMap()<>;
public void method1() {
User u1 = new User("secret", 20);
userMap.put(u1, "user1");
}
public void method2() {
User u2 = new User("secret", 20);
userMap.get(u2); // 能获取到吗?
}
2、equals()
2.1、运算符 ==
- 基本类型:比较值。
- 引用类型:比较内存地址(
equals()
默认实现)。
2.2、重写 equals()
通常认为两个对象的属性值完全相同,则对象相等。
根据这个规则来重写
equals()
即可。
示例:按以下步骤重写。
-
参数检验:
- 相同引用
- 判空
- 判断类型:根据对类型要求的严格性,选择合适的方式。
getClass()
(严格):返回运行时具体类型,不考虑继承关系。instanceOf
(宽松):测试对象是否为类的实例(或接口实现类)。
-
比较属性:先向下转型,再比较属性值。
-
基本类型:使用
==
。 -
引用类型:使用
Objects.equals()
方法,避免NPE
。@Override public boolean equals(Object obj) { if (this == obj) { return true; } // 若严格要求类型一致,条件二改成 this.getClass() != obj.getClass() if (obj == null || !obj instanceof Person) { return false; } Person p = (Person) obj; // 不使用 xxx.equals(xxx),避免NPE return Objects.equals(this.name, p.name) && this.age == p.age; }
-
Objects
工具类传入两个待比较的参数,可有效避免
NPE
。public static boolean equals(Object a, Object b) { return (a == b) || (a != null && a.equals(b)); }
3、hashCode()
3.1、含义
先了解👉 哈希及 Java 实现
- hashCode() 本质:一种哈希算法,用于哈希表(如 HashMap)。
- 意义:
- 假如没有
hashCode()
,只使用equals()
会导致频繁的重复比较。 hashCode()
提高容器存储和查找效率。
- 假如没有
3.2、要求
Java 对
hashCode()
的要求
-
在 Java 程序的一次运行期间,多次调用同一个对象的 hashCode(),必须返回一致的整数。
- 前提是该对象 equals() 比较中使用的信息没有被修改。
- 在一次运行期间必须保持一致,不同的运行期间可以不一致。
-
针对两个对象,若
equals()
相等,则hashCode()
相等。描述 成立? 说明 逆命题 若 hashCode()
相等,则equals()
相等❌ 存在哈希冲突 否命题 若 equals()
不相等,则hashCode()
不相等❌ 存在哈希冲突 逆否命题 若 hashCode
不相等,则equals()
不相等✔
3.3、重写 hashCode()
没有完美的哈希算法,hashCode() 应满足以下原则。
- 尽量减少哈希冲突。
- 哈希函数计算的结果,尽量均匀分布在哈希表的桶(
bucket
)中。
示例:使用 Objects
工具类的方法。
@Override
public int hashCode() {
return Objects.hash(name, age);
}
哈希算法
Objects.hash(...)
-
方法参数:可变参数(数组)
-
方法内容:调用
Arrays.hashCode(...)
。public static int hash(Object... values) { return Arrays.hashCode(values); }
Arrays.hashCode(...)
-
方法参数:数组
-
方法内容:计算哈希值。
-
遍历数组:基于多个元素计算。
-
迭代计算:基于迭代的方式计算(
hash = 31 * hash + hash(i)
)。public static int hashCode(Object a[]) { if (a == null) return 0; int result = 1; for (Object element : a) result = 31 * result + (element == null ? 0 : element.hashCode()); return result; }
-
优选乘子 31
选用
31
作为乘子的好处
- 奇质数:计算时可减少哈希冲突。
- 性能优化:
- 代码频繁执行时,JVM 优化
31 * i
为位运算(i<<5) - 1
。 - 位运算的执行效率高。
- 代码频繁执行时,JVM 优化