数组是Java里一个特殊的存在——它没有像String或包装类那样重写equals()/hashCode(),这篇真实验证这个"没重写"到底会带来什么后果,以及Arrays工具类如何补上这一课。
数组作为Java里唯一一种不是普通类、由JVM在运行时动态生成的引用类型,它的equals()和hashCode()到底来自哪里。
ArrayHashcodeEqualsDemo.java int[] a1 = {1, 2, 3}; int[] a2 = {1, 2, 3}; System.out.println(a1.equals(a2));
a1.equals(a2) 真实结果: false(内容相同的两个数组,equals()真实认为不相等) a1 == a2: false a1.hashCode() = 349885916 a2.hashCode() = 414493378 hashCode真实相同吗: false(同样基于身份,不基于内容)
真实结果证明数组类型没有重写 Object 默认的 equals() 和 hashCode(),继续使用的是基于对象身份(引用本身)的默认实现——即使两个数组元素完全一致,只要不是同一个数组对象,equals() 就真实是 false,hashCode() 也真实是两个不相关的值。这也是所有数组类型(包括基本类型数组和对象数组)的统一行为。
java.util.Arrays 工具类提供了专门基于内容比较的静态方法,弥补数组实例方法的这个"缺陷"。
Arrays.equals(a1, a2) 真实结果: true Arrays.hashCode(a1) == Arrays.hashCode(a2): true
真实结果证明 Arrays.equals() 和 Arrays.hashCode() 才是"看起来应该有"的基于内容的比较——逐个元素比较值是否相同、基于元素值组合计算哈希。这两个方法的存在,恰好说明了 Java 语言设计者有意让数组的实例方法保持"身份语义",把"内容语义"的能力单独放进工具类,而不是覆盖数组本身的默认行为。
这是数组身份语义在真实工程代码里最容易踩中的坑——用数组做key,内容相同但不是同一个引用时查不到。
用a1存入后,badMap.get(a2) 真实结果: null(内容相同但不是同一个数组对象,真实查不到) 用a1存入后,badMap.get(a1) 真实结果: value(同一个引用真实查得到)
二维数组的元素类型本身是数组(引用类型),Arrays.equals()对它做的仍然只是"一层"比较。
Arrays.equals(m1, m2) 真实结果(二维数组): false(Arrays.equals对二维数组只比较内层数组的引用,不够深) Arrays.deepEquals(m1, m2) 真实结果: true(deepEquals才是真正递归比较内容)
真实结果印证了系列前面 数组 clone() 只深拷贝第一层那篇验证过的同一条规律:Arrays.equals() 对 int[][] 来说,元素类型是 int[],它真实只是逐个比较这些内层数组的引用是否相等(依然是身份比较),两个内容相同但独立创建的二维数组因此真实被判定为不相等。Arrays.deepEquals() 会真实递归下探,对每一层的数组都改用基于内容的比较,这才是处理多维数组时应该使用的正确方法。
每一行都是真实运行这段代码得到的结果。