为什么在重写 equals方法的同时必须重写 hashcode方法

我们都知道Java语言是完全面向对象的,在java中,所有的对象都是继承于Object类。
其 equals 方法比较的是两个对象的引用指向的地址,hashcode 是一个本地方法,返回的是对象地址值。Ojbect类中有两个方法equals、hashCode,这两个方法都是用来比较两个对象是否相等的。

为何重写 equals方法的同时必须重写 hashcode方法呢

可以这样理解:重写了 equals 方法,判断对象相等的业务逻辑就变了,类的设计者不希望通过比较内存地址来比较两个对象是否相等,而 hashcode 方法继续按照地址去比较也没有什么意义了,索性就跟着一起变吧。

还有一个原因来源于集合。下面慢慢说~

举个例子:

在学校中,是通过学号来判断是不是这个人的。

下面代码中情景为学籍录入,学号 123 被指定给学生 Tom,学号 456 被指定给学生 Jerry,学号 123 被失误指定给 Lily。而在录入学籍的过程中是不应该出现学号一样的情况的。

根据情景需求是不能添加重复的对象,可以通过 HashSet 实现。

public class Test {
public static void main(String[] args) {
Student stu = new Student(123,"Tom");
HashSet<Student> set = new HashSet<>();
set.add(stu);
set.add(new Student(456, "Jerry"));
set.add(new Student(123, "Lily"));
Iterator<Student> iterator = set.iterator();
while (iterator.hasNext()) {
Student student = iterator.next();
System.out.println(student.getStuNum() + " --- " + student.getName());
}
}
};
class Student {
private int stuNum;
private String name;
public Student(int stuNum,String name){
this.stuNum = stuNum;
this.name = name;
}
public int getStuNum() {
return stuNum;
}
public String getName() {
return name;
}
@Override
public boolean equals(Object obj) {
if(this==obj)
return true;
if(obj instanceof Student){
if(this.getStuNum()==((Student)obj).getStuNum())
return true;
}
return false;
}
} 

输出为:

123 --- Lily
456 --- Jerry
123 --- Tom

根据输出我们发现,再次将学号 123 指定给 Lily 居然成功了。到底哪里出了问题呢?

我们看一下 HashSet 的 add 方法:

public boolean add(E e) {
return map.put(e, PRESENT)==null;
}

其实 HashSet 是通过 HashMap 实现的,由此我们追踪到 HashMap 的 put 方法:

public V put(K key, V value) {
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
if (key == null)
return putForNullKey(value);
int hash = hash(key);
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
} 

1.根据 key,也就是 HashSet 所要添加的对象,得到 hashcode,由 hashcode 做特定位运算得到 hash 码;

2.利用 hash 码定位找到数组下标,得到链表的链首;

3.遍历链表寻找有没有相同的 key,判断依据是 e.hash == hash && ((k = e.key) == key || key.equals(k))。在add Lily 的时候,由于重写了 equals 方法,遍历到 Tom 的时候第二个条件应该是 true;但是因为 hashcode 方法还是使用父类的,故而 Tom 和 Lily的 hashcode 不同也就是 hash 码不同,第一个条件为 false。这里得到两个对象是不同的所以 HashSet 添加 Lily 成功。

总结出来原因是没有重写 hashcode 方法,下面改造一下:

public class Test {
public static void main(String[] args) {
Student stu = new Student(123,"Tom");
HashSet<Student> set = new HashSet<>();
set.add(stu);
set.add(new Student(456, "Jerry"));
set.add(new Student(123, "Lily"));
Iterator<Student> iterator = set.iterator();
while (iterator.hasNext()) {
Student student = iterator.next();
System.out.println(student.getStuNum() + " --- " + student.getName());
}
}
};
class Student {
private int stuNum;
private String name;
public Student(int stuNum,String name){
this.stuNum = stuNum;
this.name = name;
}
public int getStuNum() {
return stuNum;
}
public String getName() {
return name;
}
@Override
public boolean equals(Object obj) {
if(this==obj)
return true;
if(obj instanceof Student){
if(this.getStuNum()==((Student)obj).getStuNum())
return true;
}
return false;
}
@Override
public int hashCode() {
return getStuNum();
}
} 

输出:

456 --- Jerry
123 --- Tom

重写了 hashcode 方法返回学号。OK,大功告成。

有人可能会奇怪,e.hash == hash && ((k = e.key) == key || key.equals(k)) 这个条件是不是有点复杂了,我感觉只使用 equals 方法就可以了啊,为什么要多此一举去判断 hashcode 呢?

因为在 HashMap 的链表结构中遍历判断的时候,特定情况下重写的 equals 方法比较对象是否相等的业务逻辑比较复杂,循环下来更是影响查找效率。所以这里把 hashcode 的判断放在前面,只要 hashcode 不相等就玩儿完,不用再去调用复杂的 equals 了。很多程度地提升 HashMap 的使用效率。

所以重写 hashcode 方法是为了让我们能够正常使用 HashMap 等集合类,因为 HashMap 判断对象是否相等既要比较 hashcode 又要使用 equals 比较。而这样的实现是为了提高 HashMap 的效率。

(0)

相关推荐

  • why在重写equals时还必须重写hashcode方法分享

    复制代码 代码如下: public boolean equals(Object anObject) {    if (this == anObject) {        return true;    }    if (anObject instanceof String) {        String anotherString = (String)anObject;        int n = count;        if (n == anotherString.count) { 

  • java中hashCode方法与equals方法的用法总结

    首先,想要明白hashCode的作用,必须要先知道Java中的集合. 总的来说,Java中的集合(Collection)有两类,一类是List,再有一类是Set. 前者集合内的元素是有序的,元素可以重复:后者元素无序,但元素不可重复. 那么这里就有一个比较严重的问题了:要想保证元素不重复,可两个元素是否重复应该依据什么来判断呢? 这就是Object.equals方法了.但是,如果每增加一个元素就检查一次,那么当元素很多时,后添加到集合中的元素比较的次数就非常多了. 也就是说,如果集合中现在已经有

  • java中重写equals和重写hashCode()

    java中重写equals和重写hashCode() 记得在刚上初一的时候,第一堂数学课学的是集合,那时候我知道了集合是不允许重复元素存在的. hashCode 方法用于散列集合的查找,equals 方法用于判断两个对象是否相等. 为什么重写了 equals 方法,还要重写 hashCode 方法? 因为如果只重写了 equals 方法,两个对象 equals 返回了true,但是如果没有重写 hashCode 方法,集合还是会插入元素.这样集合中就出现了重复元素了. 接下来详细分析,以 Has

  • java中重写equals()方法的同时要重写hashcode()方法(详解)

    object对象中的 public boolean equals(Object obj),对于任何非空引用值 x 和 y,当且仅当 x 和 y 引用同一个对象时,此方法才返回 true: 注意:当此方法被重写时,通常有必要重写 hashCode 方法,以维护 hashCode 方法的常规协定,该协定声明相等对象必须具有相等的哈希码.如下: (1) 当obj1.equals(obj2)为true时,obj1.hashCode() == obj2.hashCode()必须为true (2) 当obj

  • 重写equals的同时为何要重写hashCode?

    结论 先直接上结论: 重写equals不一定要重写hashCode,得看情况.如果在没使用容器时其实是没必要的. 如果使用了HashMap等容器,并且使用了自定义对象作为Key是一定要重写的. 重写equals是为了在业务逻辑上判断实例之间是否相等.重写hascode是为了让集合快速判重. hashCode()与 equals() 的规定: 1.如果两个对象相等,则 hashcode 一定也是相同的 2.两个对象相等,对两个 equals() 方法返回 true 3.两个对象有相同的 hashc

  • Java重写equals及hashcode方法流程解析

    初步探索 首先我们要了解equals方法是什么,hashcode方法是什么. equals方法 equals 是java的obejct类的一个方法,equals的源码如下: public boolean equals(Object paramObject){ return(this == paramObject); } 由此我们可以看到equals是用来比较两个对象的内存地址是否相等. hashCode方法 hashCode方法是本地方法,用于计算出对象的一个散列值,用于判断在集合中对象是否重复

  • 为何修改equals方法时还要重写hashcode方法的原因分析

    为何修改equals方法时还要重写hashcode方法 虽然在实际开发中,我们已经使用到散列集合(如HashMap),或也单独学过散列(Hash). 但是也会有很多人像我一样,看到有些时候别人写的pojo中有对对象内hashcode函数做一个重写,这就让我重新思考为什么要这么做? 下面就让我和你一起去探索一下吧! Hash是什么? Hash就是上文说到的散列,是把任意长度的输入(又叫做预映射pre-image)通过散列算法变换成固定长度的输出,该输出就是散列值.它的理论时间复杂度是可以达到O(1

  • Java中如何正确重写equals方法

    目录 1. 什么是equals方法? 1.1 equals方法: 2. 为什么要重写equals方法? 2.1 举个例子吧~ 3. 分析equals源码: 4. 正确重写equals方法: 重写equals方法的正确打开方式 正文开始@Assassin 1. 什么是equals方法? 我们首先得知道,Object类是 Java中所有类的父类(超类/基类),也就是说,在Java中,所有的类都是默认继承自Object类的,换言之,Object类中所实现的方法我们都可以直接拿来用.而equals方法便

  • 浅谈java中为什么重写equals后需要重写hashCode

    一.先看现象 public class TestDemo { public static void main(String[] args) { Person p1 = new Person("阿伦"); Person p2 = new Person("阿伦"); System.out.println(p1.equals(p2)); } static class Person { public Person(String name) { this.name = nam

  • Java中 equals 重写时为什么一定也要重写 hashCode

    目录 1.equals 方法 2.hashCode 方法 2.1 hashCode 使用 3.为什么要一起重写? 3.1 Set 正常使用 3.2 Set 集合的“异常” 3.3 解决“异常” 3.4 原因分析 总结 前言: equals 方法和 hashCode 方法是 Object 类中的两个基础方法,它们共同协作来判断两个对象是否相等.为什么要这样设计嘞?原因就出在“性能” 2 字上. 使用过 HashMap 我们就知道,通过 hash 计算之后,我们就可以直接定位出某个值存储的位置了,那

  • Java 超详细讲解hashCode方法

    目录 1.介绍一下hashCode方法 2.为什么需要hashCode方法? 3.hashCode(),equals()两种方法是什么关系? 4.为什么重写 equals 方法必须重写 hashcode 方法 ? 1.介绍一下hashCode方法 hashCode() 的作用是获取哈希码,也称为散列码,它实际上是返回一个int整数.这个哈希码的作用是确定该对象在哈希表中的索引位置. hashCode() 定义在JDK的Object.java中,这就意味着Java中的任何类都包含有 hashCo

  • 为什么在重写 equals方法的同时必须重写 hashcode方法

    我们都知道Java语言是完全面向对象的,在java中,所有的对象都是继承于Object类. 其 equals 方法比较的是两个对象的引用指向的地址,hashcode 是一个本地方法,返回的是对象地址值.Ojbect类中有两个方法equals.hashCode,这两个方法都是用来比较两个对象是否相等的. 为何重写 equals方法的同时必须重写 hashcode方法呢 可以这样理解:重写了 equals 方法,判断对象相等的业务逻辑就变了,类的设计者不希望通过比较内存地址来比较两个对象是否相等,而

随机推荐