1. 为什么重写equals()时一定要重写hashCode()?
面试回答:
java官方文档规定重写equals()时一定要重写hashCode()。 equals()和 hashCode()是 Object 类中的两个基础方法,Object 中的 equals( )和hashCode( ),使用==判断,即比较两个对象的地址值。
但是在实际的开发中我们需要比较两个字符串对象的内容是否相等,所以需要重写euqals()。重写后的equals()先比较对象的地址值,再比较字符串对象的内容(通过循环遍历每个字符)
哈希表比较两个对象是否相等,是先比较两个对象的hashcode值,所以需要重写hashcode(),(hashcode()不重写比较的是对象的引用地址,不是哈希值 ),使用hashcode()比较结果是fasle 则equals()不会再执行,若hashcode()返回true,再用equals()比较,之所以这样设计就是为了提高效率。
以上可以总结为:
两个对象使用equals比较相同,hashCode一定相同
hashCode相同,equals()比较不一定相同,可能出现hash冲突
hashCode不同,equals()一定不同
拓展:
底层基于哈希表的数据结构和类主要有以下几种:
-
HashMap :最常用的键值对存储容器,底层是数组加链表(或红黑树)实现。通过
hashCode()
将键进行哈希计算,从而快速定位键值对的存储位置。 -
HashSet :基于
HashMap
实现的无序集合,元素不能重复。它使用HashMap
的键来存储元素,值是固定的虚拟对象。 -
LinkedHashMap :继承自
HashMap
,保留插入顺序。它在HashMap
的基础上增加了一个双向链表来记录元素的插入顺序。 -
LinkedHashSet :继承自
HashSet
,同时保留元素的插入顺序。底层基于LinkedHashMap
实现。 -
Hashtable :类似
HashMap
的线程安全版本,但效率较低,已经被逐步淘汰。它也是基于哈希表实现的键值对存储。 -
ConcurrentHashMap:线程安全的哈希表实现,支持高并发环境下的键值对存储。通过分段锁机制提高并发性能。
2.举例说明
2.1例一
java官方文档规定重写equals()时一定要重写hashCode(),equals()默认是使用的Object类下的方法,使用==判断,即比较两个对象的地址值,但是在实际的开发中我们需要比较两个字符串对象的内容是否相等,所以需要重写euqals()。
举例:
如果不重写equals(),字符串内容相等,会出现误判
如:String a = "123456",String b = "123456",在 equals 方法看来,两个字符串的内存地址不相等,所以仍然会判定为不相等,这就与我们期望的判断结果发生了矛盾,所以需要去重写 equals 方法,只有重写了equals 方法之后,我们再去判断 a 和 b,此时判断结果就会相等。
2.2 例二
使用哈希表数据结构的例子:
如HashSet
Set 集合是用来保存不同对象的,相同的对象就会被 Set 合并,最终留下一份独一无二的数据。
重写equals和hashCode的情况下:
( String类底层重写过了equals()和hashCode() )
java
import java.util.HashSet;
import java.util.Set;
/**
*
*/
public class EqualsHashCodeTest {
public static void main(String[] args) {
Set<String> set = new HashSet<>();
set.add("Java");
set.add("Java");
set.add("SpringBoot");
set.add("SpringBoot");
set.add("SpringBoot");
set.add("Redis");
System.out.println("set集合的长度为:" + set.size());
set.forEach(System.out::println);
}
}
上述结果可以看出,重复的数据已经被 Set 集合"合并"了,这也是 Set 集合最大的特点:去重。
上面的案例种,set集合的泛型是String,这个类已经重写过了equals和hashCode,
那么下面我们将泛型修改为一个自定义的类,而在这个自定义的类中,只重写equals方法。
只重写equals( ),不重写hashcode( ):
java
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
/**
*
*/
class Student {
private String name;
private Integer age;
public Student(String name, Integer age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return Objects.equals(name, student.name) && Objects.equals(age, student.age);
}
// @Override
// public int hashCode() {
// return Objects.hash(name, age);
// }
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class EqualsHashCodeTest {
public static void main(String[] args) {
Set<Student> set = new HashSet<>();
set.add(new Student("张起灵", 18));
set.add(new Student("张起灵", 18));
System.out.println("set集合的长度为:" + set.size());
set.forEach(System.out::println);
}
}
从上述代码和上述图片可以看出,即使两个对象是相等的,Set 集合竟然没有将二者进行去重与合并。这就是重写了 equals 方法,但没有重写 hashCode 方法的问题所在。
解决上述问题,自然就是在重写equals的同时一定要重写hashCode!!
修改后
java
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
/**
*
*/
class Student {
private String name;
private Integer age;
public Student(String name, Integer age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return Objects.equals(name, student.name) && Objects.equals(age, student.age);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class EqualsHashCodeTest {
public static void main(String[] args) {
Set<Student> set = new HashSet<>();
set.add(new Student("张起灵", 18));
set.add(new Student("张起灵", 18));
System.out.println("set集合的长度为:" + set.size());
set.forEach(System.out::println);
}
}
过程详细分析:
通过上述结果可以看出,当我们一起重写了两个方法之后,奇迹的事情又发生了,Set 集合又恢复正常了,这是为什么呢?
出现以上问题的原因是,如果只重写了 equals 方法,那么默认情况下,Set 进行去重操作时,会先判断两个对象的 hashCode 是否相同,此时因为没有重写 hashCode 方法,所以会直接执行 Object 中的 hashCode 方法,而 Object 中的 hashCode 方法对比的是两个不同引用地址的对象(new了两次Student,那这两个对象自然就指向了不同的引用咯),所以结果是 false,那么 equals 方法就不用执行了,直接返回的结果就是 false:两个对象不是相等的,于是就在 Set 集合中插入了两个相同的对象。
但是,如果在重写 equals 方法时,也重写了 hashCode 方法 ,那么在执行判断时会去执行重写的 hashCode 方法,此时对比的是两个对象的所有属性的 hashCode 是否相同,于是调用 hashCode 返回的结果就是 true,再去调用 equals 方法,发现两个对象确实是相等的,于是就返回 true 了,因此 Set 集合就不会存储两个一模一样的数据了,于是整个程序的执行就正常了。
如果以上不明白,请你仔细阅读下面文章
文章参考:https://blog.csdn.net/weixin_43823808/article/details/124035897