Java中equals方法探讨
Java中equals()方法探讨
基本数据类型VS对象引用类型的相等性判断
- 基本数据类型:
byte、char、short、int、long、float、double
、boolean - 对象引用类型:简单来说就是
new出来的对象的名称就是一个对象引用
基本数据类型的相等性判断
特点:直接比较值是否相等,使用==运算符
1 | int a = 5; |
基本数据类型的变量直接存储值,
==直接比较两个变量的二进制值是否相等
对象引用类型的相等性判断
对象引用类型的变量相等于一个指针,利用==相当于比较“这个指针变量”的值(是一个“地址”)是否相同,而利用equals方法类似于对C语言中指针解引用之后判断指针所指向的地方的内容是否相等,并且可以重写equals()方法(个人理解,有错勿喷)
==运算符:比较内存地址
1 | String s1 = new String("hello"); |
equals()方法:比较对象内容
默认行为与==相同,但可重写实现内容比较(重写见后续内容)
1 | System.out.println(s1.equals(s2)); // true(String 类默认重写了 equals) |
查看String类的源码,可以看到重写的equals()逻辑
1 | public boolean equals(Object anObject) { |
- 常见错误
- 未重写
equals的类:对于自行创建的类的对象,默认继承原始类Object的equals方法,该方法的实现等同于==,比较地址
即Object.java中的如下方法:
1 | public boolean equals(Object obj) { |
- 数组的相等性判断:
arr1 == arr2:比较两个数组对象的地址arr1.equals(arr2):数组对象的equals()方法未重写,继承Object类的方法Arrays.equals(arr1, arr2):利用内容比较工具方法,会逐个比较数组元素的值,以下是源码片段简化
1
2
3
4
5
6
7
8
9
10public static boolean equals(int[] a, int[] a2) {
if (a == a2) return true; // 地址相同直接返回 true
if (a == null || a2 == null) return false;
if (a.length != a2.length) return false;
for (int i = 0; i < a.length; i++) {
if (a[i] != a2[i]) return false; // 逐个比较元素值
}
return true;
}
1 | int[] arr1 = {1, 2}; |
值得注意的是,如果数组对象是自创建对象,即使对于
Arrays.equals()内容比较工具方法,其底层也任然会调用自创建对象的equals()方法进行内容比较。若为重写,依旧进行地址比较
对于其他容器,List/Set,ArrayList.equals()和HashSet.equals()方法同样依赖元素的equals()方法。**Map**中HashMap的键比较同时依赖于equals()和hashCode方法
Object.equals(person a, person b)的空指针安全性
首先我们需要知道,如果不在使用a.equals(b)之前进行空指针判断而直接调用的话,会出现空指针风险。
对于a.equals(b),当a为null时,直接调用a.equals(b)会抛出NullPointerExceptionjava.util.Objects.equals(a, b) 是 Java 7 引入的静态工具方法,专门设计用于安全处理空指针。其核心逻辑如下:
1 | public static boolean equals(Object a, Object b) { |
equals()与hashCode的重写
equals()和hashCode方法一般同时重写
二者之间具有极强的依赖于契约关系:
- 哈希集合依赖:HashSet, HashMap等集合依赖hashCode定位对象,使用equals确认对象是否真正相等
- 若x.equals(y)为true,则x.hashCode() == y.hashCode()必须为true。(若
a.equals(b)为false,a.hashCode()与b.hashCode()可以相同,但应尽量避免)
以下展示了一个不同时重写的问题:
1 | class MyClass { |
正确重写方法
1 |
|
在重写时,应该尽量避免使用可变字段(无final标记的或者String等)参与
equals和hashCode方法比较
如果两个类相互依赖,再重写equals方法时仍互相依赖,可能会陷入无线递归导致栈溢出,可以为对象设置唯一标识符进行equals比较或者采取其他方法打破循环依赖(如果有多个属性,可依据实际情况对造成依赖关系的属性进行地址比较等方法)
番外
字符串常量池
在Java中,创建的字符串字面常量会被虚拟机优化存储到常量池中,与创建对象所在的堆区并不在一个地方,可能会影响==的结果。每次创建字面字符串常量时,JVM首先检查常量池中有无相同字符串常量,有的话直接用已经存在的,否则重新在常量池中创建对象,将引用传递回来。但是通过new创建的对象,则会强制在堆区创建新的变量
1 | String str1 = "hello"; // 常量池中创建 |
此外,通过intern()方法可以将字符串手动加入常量池
HashMap方法探究
首先我们来看HashMap中的equals()方法,该方法继承自AbstractMap类,源码如下:
1 | public boolean equals(Object o) { |
在比较时,一定要正确实现键和值的equals()和hashCode方法一般同时重写
- 键对象: 必须正确覆盖
hashCode()和equals()方法,确保相同的逻辑键能正确匹配。 - 值对象:如果值是自定义对象,需正确实现
equals(),否则默认比较引用地址。
此外,hashMap的键对象最好采用不可变字段,否则键对象如果发生改变,哈希值变化,但是
HashMap不会重新分配桶位置,导致后续如get()操作通过hashCode的值寻找键的时候无法找到该键
HashMap数据结构原理:
数组(桶数组)HashMap内部维护一个Node<K,V>[] table数组,每个数组元素称为一个桶(Bucket)。
- 桶的初始容量默认为
16,可通过构造函数调整,但始终是2的幂次方(优化索引计算)。
链表与红黑树- 当多个键映射到同一桶时,会以链表形式存储(拉链法)。
- 当链表长度超过
8且桶数组长度≥64时,链表转为红黑树(时间复杂度从O(n)降为O(log n))。- 当树节点数减少到
6时,退化为链表。
HashMap官方文档:https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/HashMap.html
本文参考deepseek


