为什么重写equals后建议重写hashCode?

2023年 10月 11日 50.7k 0

最近在看《Java核心技术卷一》,复习一下Java基础,谈到equals和hashCode,这个知识点我记得大学的时候是学过的,但是我竟一时语塞,回答不上来。离开校门工作一年多,学习各种框架、技术,在通往大牛的道路上乐此不疲,回过头来,反而连最基础的东西都遗忘了,让人不禁唏嘘感慨。好记性不如烂笔头,遂整理笔记记录。

首先要了解equals方法是做什么用的?

equals方法用于判断两个对象是否相等,并且可以根据实际情况自定义。
默认实现Object的equals方法。

判等不是直接用‘==’吗?

很多时候我们确实通过==来判等,不仅书写方便,也便于理解。
但是并非所有的判等操作都用==,我们需要了解一下两者的区别。

==和equals的区别

  • ==:作用于对象时,比较的是对象的引用,即内存地址。引用不同则返回false。
  • equals:在不重写的情况下,方法继承自Object。
    我们看一下Object的具体实现
public boolean equals(Object obj) {
        return (this == obj);
}

可以看到返回的就是(this == obj),即:在不重写equals的情况下,equals和‘==’是一样的。
Object认为,如果两个对象具有相同的引用, 它们一定是相等的。从这点上看,将其作为默认操作也是合乎情理的。
然而,对于多数类来说,这种判断并没有什么意义。
例如字符串,我们判等绝大部分只关心内容是否相等,指向的内存地址我们并不关心。
所以String类就重写了equals方法,String认为只要字符串的每一个字符相等,它就认定为两者相等,更加符合实际需求。

String类重写equals方法

public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        if (anObject instanceof String) {
            String anotherString = (String)anObject;
            int n = value.length;
            if (n == anotherString.value.length) {
                char v1[] = value;
                char v2[] = anotherString.value;
                int i = 0;
                while (n-- != 0) {
                    if (v1[i] != v2[i])
                        return false;
                    i++;
                }
                return true;
            }
        }
        return false;
}

我们也可以为自己的类自定义equals方法

例如:Person类,有idCode属性,在业务上,我们认为只要两个人的身份证号一致,我们就认为他们相等。
我们可以重写equals方法为:

@Override
public boolean equals(Object o) {
		if (this == o) return true;
		if (o == null || getClass() != o.getClass()) return false;
		Person person = (Person) o;
		return idCode.equals(person.getIdCode());
}

重写equals方法的规范

虽然我们可以自定义equals,但是也不能随意重写。Java语言规范要求equals方法具有下面的特性:
- 自反性:对于任何非空引用 x, x.equals(x) 应该返回 true。
- 对称性: 对于任何引用 x 和 y, 当且仅当 y.equals(x) 返回 true , x.equals(y) 也应该返回 true。
- 传递性: 对于任何引用 x、 y 和 z, 如果 x.equals(y) 返回 true, y.equals(z) 返回 true,x.equals(z) 也应该返回 true。
- 一致性: 如果 x 和 y 引用的对象没有发生变化,反复调用 x.equals(y) 应该返回同样的结果。
- 对于任意非空引用 x, x.equals(null) 应该返回 false。

现在我们讲讲hashCode

hashCode是jdk根据对象的地址或者字符串或者数字算出来的int类型的数值 详细了解请 参考 public int hashCode()返回该对象的哈希码值。支持此方法是为了提高哈希表(例如 java.util.Hashtable 提供的哈希表)的性能。

和equals一样,如果类不重写hashCode方法,将默认实现Object的hashCode方法。

public native int hashCode();

这是一个本地接口,虚拟机将和操作系统交互,得到对象引用的内存地址经过哈希算法返回一个整型值。
所以只要对象引用不同,hashCode也将会不同。

hashCode的通用约定

  • 在一个应用程序执行期间,如果一个对象的equals方法做比较所用到的信息没有被修改的话,那么,对该对象调用hashCode方法多次,它必须始终如一地返回 同一个整数。在同一个应用程序的多次执行过程中,这个整数可以不同,即这个应用程序这次执行返回的整数与下一次执行返回的整数可以不一致。
  • 如果两个对象根据equals(Object)方法是相等的,那么调用这两个对象中任一个对象的hashCode方法必须产生同样的整数结果。
  • 如果两个对象根据equals(Object)方法是不相等的,那么调用这两个对象中任一个对象的hashCode方法,不要求必须产生不同的整数结果。然而,程序员应该意识到这样的事实,对于不相等的对象产生截然不同的整数结果,有可能提高散列表(hash table)的性能。

由此可见,如果重写了equals而不重写hashCode,则会导致两个对象的equals返回true,但是hashCode返回false,将会违反第二条规定。

同时对于HashSet和HashMap这些基于散列值(hash)实现的类。HashMap的底层处理机制是以数组的方法保存放入的数据的(Node[] table),其中的关键是数组下标的处理。数组的下标是根据传入的元素hashCode方法的返回值再和特定的值异或决定的。

很多时候,由于equals判断对象相等,我们期望HashSet能覆盖之前的对象,但是由于hashCode不等,HashSet会不断的add,导致和我们预期的不一致。

HashMap类获取对象的get方法,也是先比较key的hashCode,如果hashCode返回不一致,将会得到null。

为了程序严谨,减少不必要的错误,还是建议重写equals的同时也重写hashCode方法。

相关文章

JavaScript2024新功能:Object.groupBy、正则表达式v标志
PHP trim 函数对多字节字符的使用和限制
新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
为React 19做准备:WordPress 6.6用户指南
如何删除WordPress中的所有评论

发布评论