前言
hashCode()与equals() 是java面试中经常被问道的问题,可能会被问道 “你重写过 hashcode 和 equals 么,为什么重写equals时必须重写hashCode方法?” ,因此我们需要彻底弄清楚这2个方法的关联关系。
hashCode()方法介绍
hashCode()的作用是获取哈希码,也称为散列码,它实际上是返回一个int整数,这个hash码的作用是确定该对象在哈希表中的索引位置。hashCode() 定义在JDK的Object.java中,这就意味着Java中的任何类都包含有hashCode() 函数。
equals()方法介绍
equals它的作用也是判断两个对象是否相等,如果对象重写了equals()方法,比较两个对象的内容是否相等;如果没有重写,比较两个对象的地址是否相同,等价于“==”。同样的,equals()定义在JDK的Object.java中,这就意味着Java中的任何类都包含有equals()函数。
hashCode() 和 equals() 之间的关系
从类的用途这个角度来分析hashCode() 和 equals() 之间的关系
1、不会创建“类对应的散列表”
这里所说的“不会创建类对应的散列表”的意思是:我们不会在HashSet, HashTable, HashMap等等这些本质是散列表的数据结构中,用到该类。例如,不会创建该类的HashSet集合。
在这种情况下,该类的“hashCode() 和 equals() ”没有半毛钱关系!
equals() 用来比较该类的两个对象是否相等,而hashCode() 则根本没有任何作用,所以,不用理会hashCode()。
2、会创建“类对应的散列表”
这里所说的“会创建类对应的散列表”是说:我们会在HashSet, HashTable, HashMap等等这些本质是散列表的数据结构中,用到该类。例如,创建该类的HashSet集合。
在这种情况下,该类的“hashCode() 和 equals() ”是有关系的:
2.1、如果两个对象相等,那么它们的hashCode()值一定相同。这里的相等是指,通过equals()比较两个对象时返回true。
2.2、如果两个对象hashCode()相等,它们并不一定相等。因为在散列表中,hashCode()相等,即两个键值对的哈希值相等。然而哈希值相等,并不一定能得出键值对相等,此时就出现所谓的哈希冲突场景。
举例子说明:
Person类,只重写equals()方法
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age && Objects.equals(name, person.name);
}
}
测试类
public class DemoConflictTest {
public static void main(String[] args) {
Person p1 = new Person("eee", 10);
Person p2 = new Person("eee", 10);
Person p3 = new Person("aaa", 20);
HashSet<Person> hashSet = new HashSet<>();
hashSet.add(p1);
hashSet.add(p2);
hashSet.add(p3);
System.out.printf("p1.equals(p2) : %s; p1(%d) p2(%d)\n", p1.equals(p2), p1.hashCode(), p2.hashCode());
System.out.printf("set:%s\n", hashSet);
}
}
结果
p1.equals(p2) : true; p1(21685669) p2(2133927002)
set:[Person{name='aaa', age=20}, Person{name='eee', age=10}, Person{name='eee', age=10}]
结果分析:
Person类重写了equals方法,但是HashSet中仍然有重复元素p1和p2。是因为虽然p1和p2的内容相等,但是他们的hashCode不等,所以hashSet在添加p1和p2的时候,认为他们不相等。
为什么HashSet会用到hashCode()呢?
hashSet 底层使用的是hashMap的put方法,而hashMap的put方法,使用hashCode()用key作为参数计算出hash值,然后进行比较,如果相同,再通过equals()比较key值是否相同,如果相同,返回同一个对象。
所以,如果类使用在散列表的集合对象中,要判断两个对象是否相同,除了要覆盖equals()之外,也要覆盖hashCode()函数。否则,equals()无效。
经典面试题
hashCode相等,两个对象一定相等嘛?equals相等,hashCode一定相等嘛?
结论:
hashCode相等,equals也不一定相等, 两个类也不一定相等;
equals相同, 说明是同一个对象, 那么hashCode一定相同。
哈希表是结合了直接寻址和链式寻址两种方式,所需要的就是将需要加入哈希表的数据首先计算哈希值,其实就是预先分个组,然后再将数据挂到分组后的链表后面,随着添加的数据越来越多,分组链上会挂接更多的数据,同一个分组链上的数据必定具有相同的哈希值,java中的hash函数返回的是int类型的,也就是说,最多允许存在2^32个分组,也是有限的,所以出现相同的哈希码就不稀奇了。