Java中HashCode()和equals()的作用
引言
我们知道Java中的集合(Collection)大致可以分为两类,一类是List,再有一类是Set。
前者集合内的元素是有序的,元素可以重复;后者元素无序,但元素不可重复。
这里就引出一个问题:要想保证元素不重复应该依据什么来判断呢?
为什么要用hashCode()?
为了解决放入重复数据的问题,一开始开发者们想到了用Object.equals方法。
但是,很快他们发现如果每增加一个元素就检查一次,那么当元素很多时,后添加到集合中的元素比较的次数就非常多了。
也就是说,如果集合中现在已经有1000个元素,那么第1001个元素加入集合时,它就要调用1000次equals方法。这显然会大大降低效率。
于是,Java采用了哈希表的原理。哈希(Hash)实际上是个人名,由于他提出“哈希算法”的概念,所以就以他的名字命名了。
哈希算法也称为散列算法,哈希值也称为散列码,实际上就是将数据依照哈希算法直接指定到一个地址上。
初学者可以简单理解,hashCode方法实际上返回的就是对象存储的物理地址(实际可能并不是)。
这样一来,当集合要添加新的元素时,先调用这个元素的hashCode方法,就一下子能定位到它应该放置的物理位置上。
如果这个位置上没有元素,它就可以直接存储在这个位置上,不用再进行任何比较了;
如果这个位置上已经有元素了,就调用它的equals方法与新元素进行比较,相同的话就不存了,不相同就散列其它的地址。
所以这里存在一个冲突解决的问题。这样一来实际调用equals方法的次数就大大降低了,几乎只需要一两次。
两个方法的作用
equals()作用:用于判断其他对象是否与该对象相同;
Object类中是这样定义equals():
public boolean equals(Object obj) {
return (this == obj);
}
很显然,在Object类原生代码中比较的是引用地址,但是需要提醒的一点是在String、Math、Integer、Double等封装类中都对equals()进行了不同程度的重写以满足其不同需要,例如在String类中:
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) {
char v1[] = value;
char v2[] = anotherString.value;
int i = offset;
int j = anotherString.offset;
while (n– != 0) {
if (v1[i++] != v2[j++])
return false;
}
return true;
}
}
return false;
}
显然,在String类中的equals()比较的不再是引用对象的地址而是内容,在Java8种基本数据类型中equals()比较的都是内容,其实就是数值。
HashCode()作用:给不同对象返回不同的hash code值,相当于识别码;
使用HashCode()时应当符合以下三点:
- 在一个Java应用的执行期间,如果一个对象提供给equals做比较的信息没有被修改的话,该对象无论调用多少次hashCode(),必须始终返回同一个integer;
- 如果两个对象根据equals(Object)方法是相等的,那么调用二者各自的hashCode()必须产生同一个integer结果;
- 调用equals(java.lang.Object)方法结果不相等的两个对象,调用二者各自hashCode()不一定不相同,可能相同,可能不同。
重点
在集合查找时,使用hashcode无疑能大大降低对象比较次数,提高查找效率!
Java对象的eqauls方法和hashCode方法是这样规定的:
1、相等(相同)的对象必须具有相等的哈希码(或者散列码)。
2、如果两个对象的hashCode相同,它们并不一定相同。
可能的困惑
一、相等(相同)的对象必须具有相等的哈希码(或者散列码),为什么?
假设A与B这两个对象相等,即他们equals的结果为true,但他们各自的哈希码不相同,但他们要存入同一个HashMap时,有可能就会因为哈希码不同导致计算得出的HashMap内部数组位置索引不一样,那么A、B很可能同时存入同一个HashMap中,但我们知道HashMap是不允许存放重复元素的。
二、为什么两个对象的hashCode相同他们也不一定相同?
你的问题其实也是在说不同对象的hashCode有可能相同,产生这种结果的原因我个人觉得是由于“哈希算法”在生产哈希码时造成的,两个对象在某些方面具有高度一致性。正因为考虑到可能会出现这样的情况,所以HashMap在添加两个hashCode完全相同的对象时会在此哈希码指定的内部数组的位置索引处建立一个新的链表,然后将两个对象串起来放在该位置,这样就能在保证虽然hashCode相同仍能存入HashMap中,当然前提是他们调用equals()的返回值为false。
再补充一点,在业界中有一个专门的术语去描述这种现象,我们称之为哈希冲突,很显然,虽然哈希冲突是可以解决的,但没有人会希望经常看到它。
实际操作
在实际编写代码程序时,我们经常会被要求重写hashCode()和equals(),曾经我也对这个问题百思不得其解,但现在我也能向大家解释这其中的秘密了。
以HashSet为例,我们知道HashSet是继承Set接口,而Set接口由实现了Collection接口,HashSet中不允许出现重复值,而且元素的位置也是不确定的。
那么在这里介绍一下Java集合判断两个对象是否相等的规则是:
1.首先要判断两个对象的hashCode是否相等;
如果相等,进入第二步再判断;
如果不相等,那么认为两个对象也不相等,结束判断。
2.判断两个对象用equals()是否相等。
如果这次判断也相等,则认为两个对象相等;
如果不相等,那么认为两个对象也不相等。
为什么要进行两次判断呢?
可以不进行第一次的判断,但如果没有,实际使用效率会大大降低,尤其是在进行大量数据比较时。其实前面在介绍hashCode()时有过提及,即hashCode()相等时,equals()也可能不相等,所以我们就加上了第二条判断进行限制。总的来说,就是可以没有第一条判断,但必须要有第二条判断,但在实际开发中两条都最好写上,一旦出现大量数据需要判断时,仅靠equals()进行判断的话执行效率会大打折扣。
代码展示
package Exercise;
import java.util.HashSet;
public class e1 {
public static void main(String[] args) {
HashSet hs=new HashSet();
hs.add(new Student(1,"张三"));
hs.add(new Student(2,"李四"));
hs.add(new Student(3,"王麻子"));
hs.add(new Student(1,"张三"));
for (Object object : hs) {
System.out.println(object);
}
}
}
class Student{
int num;
String name;
Student(int num,String name){
this.name=name;
this.num=num;
}
public String toString(){
return num+":"+name;
}
}
运行结果:
为什么Hashset添加了相等的元素呢,这是不是和Hashset的原则违背了呢?回答是:没有。因为在根据hashCode()对两次建立的new Student(1,“张三 ”)对象进行比较时,生成的是不同的哈希码值,所以Hashset把他当作不同的对象对待了,当然此时的equals()方法返回的值也不等。
为什么会生成不同的哈希码值呢?原因就在于我们自己写的Student类并没有重新自己的hashCode()和equals()方法,所以在比较时,是继承的object类中的hashCode(),而object类中的hashCode()是一个本地方法,比较的是对象的地址(引用地址),使用new方法创建对象,两次生成的当然是不同的对象了,造成的结果就是两个对象的hashCode()返回的值不一样,所以Hashset会把它们当作不同的对象对待。
怎么解决这个问题呢?答案是:在Student类中重写hashCode()和equals()方法。
package Exercise; import java.util.HashSet; public class e1 {
public static void main(String[] args) {
HashSet hs=new HashSet();
hs.add(new Student(1,"张三"));
hs.add(new Student(2,"李四"));
hs.add(new Student(3,"王麻子"));
hs.add(new Student(1,"张三"));
for (Object object : hs) {
System.out.println(object);
}
}
}
class Student{
int num;
String name;
Student(int num,String name){
this.name=name;
this.num=num;
} @Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
result = prime * result + num;
return result;
} @Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Student other = (Student) obj;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
if (num != other.num)
return false;
return true;
} public String toString(){
return num+":"+name;
}
}
运行结果
可以看到重复元素的问题已经消除,根据重写的方法,即便两次调用了new Student(1,"张三"),我们在获得对象的哈希码时,根据重写的方法hashCode(),获得的哈希码肯定是一样的,当然根据equals()方法我们也可判断是相同的,所以在向hashset集合中添加时把它们当作重复元素看待了。
刚才使用的重写是用快捷键进行的,我们也可以手敲,不过写这么多就没必要了。
手敲重写代码:
public int hashCode(){
return num * name.hashCode();
}
public boolean equals(Object o){
Student s = (Student) o;
return num == s.num && name.equals(s.name);
}
做个总结
- 重点是equals,重写hashCode只是技术要求(为了提高效率);
- 为什么要重写equals呢?因为在Java的集合框架中,是通过equals来判断两个对象是否相等的;
- 在hibernate中,经常使用set集合来保存相关对象,而set集合是不允许重复的。在向HashSet集合中添加元素时,其实只要重写equals()这一条也可以。但当hashset中元素比较多时,或者是重写的equals()方法比较复杂时,我们只用equals()方法进行比较判断,效率也会非常低,所以引入了hashCode()这个方法,只是为了提高效率,且这是非常有必要的。
如果hashCode()这样写:
public int hashCode(){
return 1; //等价于hashcode无效
}
这样做的效果就是在比较哈希码的时候不能进行判断,因为每个对象返回的哈希码都是1,每次都必须要经过比较equals()方法后才能进行判断是否重复,这当然会引起效率的大大降低。
文章参考:
在Java中正确地使用equals()和hashCode()方法
Java中equals()与hashCode()方法详解
深入解析Java对象的hashCode和hashCode在HashMap的底层数据结构的应用
Java hashCode() 和 equals()的若干问题解答
Java中HashCode()和equals()的作用的更多相关文章
- java 中hashcode和equals 总结
一.概述 在Java中hashCode的实现总是伴随着equals,他们是紧密配合的,你要是自己设计了其中一个,就要设计另外一个.当然在多数情况下,这两个方法是不用我们考虑的,直 ...
- Java中hashcode,equals和==
hashcode方法返回该对象的哈希码值. hashCode()方法可以用来来提高Map里面的搜索效率的,Map会根据不同的hashCode()来放在不同的位置,Map在搜索一个对象的时候先通过has ...
- java中hashcode和equals的区别和联系
HashSet和HashMap一直都是JDK中最常用的两个类,HashSet要求不能存储相同的对象,HashMap要求不能存储相同的键. 那么Java运行时环境是如何判断HashSet中相同对象.Ha ...
- java中hashCode()与equals()详解
首先之所以会将hashCode()与equals()放到一起是因为它们具备一个相同的作用:用来比较某个东西.其中hashCode()主要是用在hash表中提高 查找效率,而equals()则相对而言使 ...
- 深入探究Java中hashCode()和equals()的关系
目录 一.基础:hashCode() 和 equals() 简介 equals() hashCode() 二. 漫谈:初识 hashCode() 与 equals() 之间的关系 三. 解密:深入理解 ...
- Java中hashCode、equals、==的区别
ref:http://www.cnblogs.com/skywang12345/p/3324958.html 1.==作用: java中的==用来判断两个对象的地址是否相等:当对象是基本数据类型时,可 ...
- java中hashcode()和equals()的详解
今天下午研究了半天hashcode()和equals()方法,终于有了一点点的明白,写下来与大家分享(zhaoxudong 2008.10.23晚21.36). 1. 首先equals()和hashc ...
- java中 hashCode() 和 equals()
1. 值类型是存储在内存中的栈,而引用类型的变量在栈中仅仅是存储引用类型变量的地址来自堆,而其本身则存储在栈中. 2. ==操作比较的是两个变量的值是否相等, 3. 对于引用型变量表示的是两个变量在堆 ...
- java中hashCode和equals什么关系,hashCode到底怎么用的
Object类的hashCode的用法:(新手一定要忽略本节,否则会很惨) 马 克-to-win:hashCode方法主要是Sun编写的一些数据结构比如Hashtable的hash算法中用到.因为ha ...
随机推荐
- 代码中如何优化过多的if..else
针对代码中,过多的 if ... else ..,如何优化减少if else呢?(非常重要的优化技巧) 缺点:过多的if else 导致阅读不方便,逻辑过于复杂,代码多长. 解决方法:可以采用多个方 ...
- VRRP的基本配置
一.实验拓扑 二.实验编址 三.实验步骤: 1.设置PC的IP等信息 2.启动设备(全选) 3.设置路由器端口ip 4. 配置ospf网络 查看: 5.配置VRRP协议 配置完成后查看: 6.验证主备 ...
- STM32—SPI详解
目录 一.什么是SPI 二.SPI协议 物理层 协议层 1.通讯时序图 2.起始和停止信号 3.数据有效性 4.通讯模式 三.STM32中的SPI 简介 功能框图 1.通讯引脚 2.时钟控制逻辑 3. ...
- 【硬件模块】华为NBIOT 使用记录
From: https://liudongdong1.github.io/ 1. background Low power wide area network (LPWAN) has become a ...
- C#托管堆和垃圾回收
垃圾回收 值类型 每次使用都有对应新的线程栈 用完自动释放 引用类型 全局公用一个堆 因此需要垃圾回收 操作系统 内存是链式分配 CLR 内存连续分配(数组) 要求所有对象从 托管堆分配 GC 触发条 ...
- C#多线程---Mutex类实现线程同步
一.例子 1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 ...
- Javascript - Vue - 动画
动画状态类名 vue动画通过将需要执行动画的标签放入transition标签中,再通过设置预置的vue动画类名的css样式来控制动画的呈现效果. 开场动画状态的三个类名 v-enter:动画开始之前的 ...
- 将svn项目导出,再导入到其他工作空间
方法一: 对于一致svn地址,本地没有的项目,直接eclipse中svn检出即可. 若本地有项目,但想导入到另一个工作空间(即拷贝一份,不想再从svn拉),则需要用export方法. 方法二(expo ...
- 统计学习:线性可分支持向量机(SVM)
模型 超平面 我们称下面形式的集合为超平面 \[\begin{aligned} \{ \bm{x} | \bm{a}^{T} \bm{x} - b = 0 \} \end{aligned} \tag{ ...
- spring boot应用常用配置
pom.xml <!--自动打包--> <plugin> <groupId>org.springframework.boot</groupId> < ...