Java重写equals方法时为什么要重写hashCode方法

在我们平时编写Java代码时,重写equals方法时一定要重写hashCode方法,这是为什么呢?

在讨论这个问题前,我们先看下Object类中hashCode方法和equals方法。
hashCode方法:
在这里插入图片描述
翻译如下:
在这里插入图片描述
equals方法:
在这里插入图片描述
翻译如下:
在这里插入图片描述

1、hashCode方法的作用

在Java中也一样,hashCode方法的主要作用是为了配合基于散列的集合一起正常运行,这样的散列集合包括HashSet、HashMap以及HashTable。

为什么这么说呢?考虑一种情况,当向集合中插入对象时,如何判别在集合中是否已经存在该对象了?

也许大多数人都会想到调用equals方法来逐个进行比较,这个方法确实可行。但是如果集合中已经存在一万条数据或者更多的数据,如果采用equals方法去逐一比较,效率必然是一个问题。

此时hashCode方法的作用就体现出来了,当集合要添加新的对象时,先调用这个对象的hashCode方法,得到对应的hashcode值,实际上在HashMap的具体实现中会用一个table保存已经存进去的对象的hashcode值,如果table中没有该hashcode值,它就可以直接存进去,不用再进行任何比较了;如果存在该hashcode值, 就调用它的equals方法与新元素进行比较,相同的话就不存了,不相同就散列其它的地址,所以这里存在一个冲突解决的问题,这样一来实际调用equals方法的次数就大大降低了,说通俗一点:Java中的hashCode方法就是根据一定的规则将与对象相关的信息(比如对象的存储地址,对象的字段等)映射成一个数值,这个数值称作为散列值。
java.util.HashMap的中put方法的具体实现,先计算key的hash值,从table数组中取出对应节点,如果节点不存在则添加一个节点;如果存在则更新value,返回旧value。
在这里插入图片描述
hash方法会调用对象的hashCode()方法:
在这里插入图片描述
addEntry方法添加新节点:
在这里插入图片描述
new一个Entry实例,next指向原有的Entry实例。也就是新new的Entry实例是该链表的头。
在这里插入图片描述
Entry是一个静态内部类,有一个属性next,指向下一个Entry,形成一个链表结构。
在这里插入图片描述

2、equals方法和hashCode方法

看下面代码:

package com.test.hashcode;
import java.util.HashMap;
public class Test {
	
	public static void main(String[] args) {
		Student s1 = new Student("Tom", 12);
		Student s2 = new Student("Tom", 12);
		System.out.println("s1和s2是同一个人吗?"+s1.equals(s2));
		System.out.println("s1 hashCode:"+s1.hashCode());
		System.out.println("s2 hashCode:"+s2.hashCode());		     
		HashMap<Student, Integer> hashMap = new HashMap<Student, Integer>();
		hashMap.put(s1, 1);
		System.out.println(hashMap.get(s2));
	}
}

class Student {
    private String name;
    private int age;
   
    public Student(String name, int age) {
		this.name = name;
		this.age = age;
	}

	@Override
    public boolean equals(Object o) {
       if(this == o) {
    	   return true;
       }
       if(o == null || getClass() != o.getClass()) {
         return false;
       }
       Student s = (Student) o;
       return age == s.age && name.equals(s.name);
    }

}

输出结果:

s1和s2是同一个人吗?true
s1 hashCode:1118484607
s2 hashCode:1446427658
null

我们Student类重写了equals方法,hashCode方法没有重写,s1和s2的姓名和年龄相同,equals方法为true,认为是同一个人。但是s1和s2的hashCode返回不同。

我们看下hashMap的get方法,先获取key的hashCode,由于s1和s2的hashCode不同,所以hashMap.get(s2)得到的是null。
在这里插入图片描述
在这里插入图片描述
接下来我们重写下Student类的hashCode方法,让equals方法和hashCode方法始终在逻辑上保持一致性。

@Override
public int hashCode() {
   return name.hashCode()+age*20+17;
}

重新运行,输出结果如下,s1和s2的hashCode相同了,hashMap.get(s2)得到了1。

s1和s2是同一个人吗?true
s1 hashCode:84531
s2 hashCode:84531
1

① 在 Java 应用程序执行期间,在对同一对象多次调用 hashCode 方法时,必须一致地返回相同的整数,前提是将对象进行 equals 比较时所用的信息没有被修改。从某一应用程序的一次执行到同一应用程序的另一次执行,该整数无需保持一致。

② 如果根据 equals(Object) 方法,两个对象是相等的,那么对这两个对象中的每个对象调用 hashCode 方法都必须生成相同的整数结果。

③ 如果根据 equals(java.lang.Object) 方法,两个对象不相等,那么对这两个对象中的任一对象上调用 hashCode 方法不 要求一定生成不同的整数结果。但是,程序员应该意识到,为不相等的对象生成不同整数结果可以提高哈希表的性能。

如果hashCode方法依赖于对象中易变的数据,用户就要当心了,因为此数据发生变化时,hashCode()方法就会生成一个不同的hash值。看下面例子:

package com.test.hashcode;

import java.util.HashMap;

public class Test {
	
	public static void main(String[] args) {
		Student s1 = new Student("Tom", 12);
		System.out.println("s1 hashCode:"+s1.hashCode());
		     
		HashMap<Student, Integer> hashMap = new HashMap<Student, Integer>();
		hashMap.put(s1, 1);
		
		s1.setAge(13);
		System.out.println("s1 hashCode:"+s1.hashCode());
		System.out.println(hashMap.get(s1));
	}
}

class Student {
    private String name;
    private int age;
   
    public Student(String name, int age) {
		this.name = name;
		this.age = age;
	}

	@Override
    public boolean equals(Object o) {
       if(this == o) {
    	   return true;
       }
       if(o == null || getClass() != o.getClass()) {
         return false;
       }
       Student s = (Student) o;
       return age == s.age && name.equals(s.name);
    }

	@Override
	public int hashCode() {
	   return name.hashCode()+age*20+17;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}
	
}

输出结果:

s1 hashCode:84531
s1 hashCode:84551
null

修改了age属性的值,导致hashCode变化,所以输出为“null”。

因此,在设计hashCode方法和equals方法的时候,如果对象中的数据易变,则最好在equals方法和hashCode方法中不要依赖于该字段。

欢迎小伙伴们关注转发点赞,谢谢~~
浏览更多文章可关注微信公众号:diggkr

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值