参考答案
本篇深入解析:为什么重写equals,就要重写hashCode?
1. equals方法
Object类中默认的实现方式是:return this ==obj ,即:只有this和obj引用同一个对象,才会返回true。
但是,实际上我们需要用equals来判断 2个对象是否等价,而非验证他们的唯一性,在实现自己的类时,就要重写equals。
按照约定,equals需要满足以下规则。
- 自反性: x.equals(x) 一定是true
- 对null: x.equals(null) 一定是false
- 对称性: x.equals(y) 和 y.equals(x)结果一致
- 传递性: a 和 b equals , b 和 c equals,那么 a 和 c也一定equals。
- 一致性: 在某个运行时期间,2个对象的状态的改变不会影响equals的决策结果,那么,在这个运行时期间,无论调用多少次equals,都返回相同的结果。
重写equals方法的实例:
class Test { private int num; private String data; public boolean equals(Object obj) { if (this == obj) return true; if ((obj == null) || (obj.getClass() != this.getClass())) return false; //能执行到这里,说明obj和this同类且非null。 Test test = (Test) obj; return num == test.num&& (data == test.data || (data != null && data.equals(test.data))); } public int hashCode() { //重写equals,也必须重写hashCode。具体后面介绍。 } }
2. hashCode方法
这个方法返回对象的散列码,返回值是int类型的散列码。对象的散列码,是为了更好的支持基于哈希机制的Java集合类。
例如:Hashtable、HashMap、HashSet 等。
关于hashCode方法,一致性约定如下:
- 在某个运行时期间,只要对象的(字段的)变化不会影响equals方法的决策结果,那么,在这个期间,无论调用多少次hashCode,都必须返回同一个散列码。
- 如果2个对象通过equals调用后返回是true,那么这个2个对象的has
- hCode方法也必须返回同样的int型散列码如果2个对象通过equals返回false,他们的hashCode返回的值允许相同。(然而,程序员必须意识到,hashCode返回独一无二的散列码,会让存储这个对象的hashtables更好地工作。)
重写了euqls方法的对象,就必须同时重写hashCode()方法。
3. 必须重写hashCode方法的原因
上述示例,Test类对象有2个字段,num和data,这2个字段代表了对象的状态,用在equals方法中作为评判的依据。
在hashCode方法中,这2个字段也要参与hash值的运算,作为hash运算的中间参数。
这点很关键,这是为了遵守:2个对象equals。
那么 hashCode的规则也一定是相同的。即:参与equals函数的字段,也必须都参与hashCode 的计算。
4. 重写hashCode注意事项
重写hashCode方法时,除了上述一致性约定,还需要注意:
- 返回的hash值是int型的,防止溢出;
- 不同的对象返回的hash值应该尽量不同。(为了hashMap等集合的效率问题)
- 《Java编程思想》中提到一种情况:“设计hashCode()时最重要的因素就是,无论何时,对同一个对象调用hashCode()都应该产生同样的值。如果在讲一个对象用put()添加进HashMap时产生一个hashCdoe值,而用get()取出时却产生了另一个hashCode值,那么就无法获取该对象了。所以如果你的hashCode方法依赖于对象中易变的数据,用户就要当心了,因为此数据发生变化时,hashCode()方法就会生成一个不同的散列码。”
实例:
public class Test { private int num; private String data; public Test(int num,String data){ this.num = num; this.data = data; } public void setNum(int num) { this.num = num; } public boolean equals(Object obj) { if (this == obj) return true; if ((obj == null) || (obj.getClass() != this.getClass())) return false; Test test = (Test) obj; return num == test.num&& (data == test.data || (data != null && data.equals(test.data))); } public int hashCode() { int hash = 7; hash = 31*hash+num; hash = 31*hash+data.hashCode(); return hash; } public static void main(String[] args) { Map<Test,Integer> map = new HashMap<>(); Test t1 = new Test(21,"ouym"); map.put(t1, 1); t1.setNum(20); System.out.println(map.get(t1)); } }
输出值为null,hashMap取不到值了。
不重写equals和hashCode方法,是不依赖于对象属性的变化的,意思是这里使用默认的hashCode方法可以取到值。
我们重写equal方法的初衷,是判定name和num属性都相等的Test对象是相等的,而不是说同一个对象的引用才相等。
而num=21和num=20明显不想等,所以这里hashCode返回值不同并不违背设计的初衷。
要特别注意下示例代码的使用陷阱。
以上,是Java面试题【为什么重写equals,就要重写hashCode】的参考答案。
相关面试题:
输出,是最好的学习方法。
欢迎在评论区留下你的问题、笔记或知识点补充~
—end—