java中hashCode()与equals()详解
首先之所以会将hashCode()与equals()放到一起是因为它们具备一个相同的作用:用来比较某个东西。其中hashCode()主要是用在hash表中提高 查找效率,而equals()则相对而言使用更广泛,用于比较两个对象的值是否相同,在Java集合框架中它们共同出现用来比较某元素是否相等。
一hashCode()
hashCode()位于Object类中,其定义如下:
public native int hashCode();
从上述的定义可以看到hashCode()属于本地方法。
1为何需要hashCode()或者说hashCode()的作用:
我们知道当判断两个对象是否相同时我们时我们可以使用equals()方法,那么为何需要hashCode()呢?其实从名字上就可以看出hashCode的作用是为hahs表准备的,是为了提高查找效率而存在的,如在java集合框架中的HashSet,HashMap。
那么当我们往集合中添加一个元素的时候如何判断该元素是否存在呢?(注意java集合框架中除了ArrayList与LinkedList外都不允许存在重复元素,对于Map集合重复指的是K/V都相同)如果不用hashCode(),那么你可能会想到用equals()方法将集合中已存在的元素与待插入元素一个一个比较,但是这样做显然效率低下,因此hashCode()应运而生,hashCode()就是为了解决元素是否重复而存在的,它采用一定的规则将和对象相关的信息(比如对象的存储地址,对象的字段等)映射成一个数值,这个数值称作为散列值。它用来标识一个对象,如果hash值不同则表示这两个对象一定不同,如果hash值相同,则这两个对象可能相同也可能不同,此时需要使用equals()来判断这两个对象是否相同注意:equals()才是用来判断两个对象是否相同的核心,hashCode()只是为了提高在集合中的查找效率而存在的,只要hashCode值不同则这两个对象一定不同,如在HashMap的put函数中,通过hash的值来判断是否存在该元素,如果hash值不存在(tab[i]==null),则一定不存在该元素,若hash值存在,则可能存在该元素,需要通过equals方法来确定,如果hash值存在且key.equals.(k)则表明存在该元素,直接更新其值,否则表明不存在,则采用链表或红黑树的方式将元素添加到tab[i]对应的链表或红黑树中,这样的话就能大大减少使用equals的次数,从而提高效率。HashMap的put函数源码如下:
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)//1
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)//2首先判断tab[(n - 1) & hash]处是否为空,如果是代表该数组下标为[(n - 1) & hash]的位置无元素,可直接put
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))//如果Hash值相同,则调用equals方法来确定是否存在该元素,则执行break语句
break;//跳出for循环,执行下面的if语句,即<span style="font-family: Arial, Helvetica, sans-serif;">existing mapping for key,则更新value的值,e.value=value。</span>
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);//
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
可以看到先通过hash得到插入的数组索引i,如果tab[i]==null,表示此下标处无元素存在,可直接添加元素,否则出现冲突,扫描链表或红黑树,在此过程中通过equals方法来确定是否存在该元素,如果存在,则直接更新,否则采用链表或红黑树的方式将元素添加到tab[i]对应的链表或红黑树中。
即通过hash的值来判断是否存在该元素,如果hash值不存在(tab[i]==null),则一定不存在该元素,若hash值存在,则可能存在该元素,需要通过equals方法来确定,如果hash值存在且key.equals.(k)则表明存在该元素,直接更新其值,否则表明不存在,则采用链表或红黑树的方式将元素添加到tab[i]对应的链表或红黑树中
那么我们能否仅仅根据hashCode()的值来判断两个对象是否相等呢?答案是不能,因为不同的对象可能会生成相同的hashcode值。虽然不能根据hashcode值判断两个对象是否相等,但是可以直接根据hashcode值判断两个对象不等,如果两个对象的hashcode值不等,则必定是两个不同的对象。如果要判断两个对象是否真正相等,必须通过equals方法。
也就是说对于两个对象,如果调用equals方法得到的结果为true,则两个对象的hashcode值必定相等;
如果equals方法得到的结果为false,则两个对象的hashcode值不一定不同;
如果两个对象的hashcode值不等,则equals方法得到的结果必定为false;
如果两个对象的hashcode值相等,则equals方法得到的结果未知。
二equals()
equals()同样也是位于Object类中的一个方法,我们来看一下其源码:
public boolean equals(Object obj) {
return (this == obj);
}
可以看到,Object类中的equals方法是用来判断两个对象的地址是否相等(this==obj),但是我们知道在使用String类的时候equals比较的是两个字符串的内容是怎么回事呢?这时因为String重写了Object类的equals(),因为对于字符串而言我们更关心其内容,而不是其对象地址,,我们来看一下String类中的equals()源码:
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
我们可以看到,在String的equals()中首先判断这两个对象是否相同,若相同则直接返回true(因为同一个地址的String其存储的内容一定相同),若这两个对象不同则首先获取这两个字符串的长度,如果长度相同则逐个比较每个字符是否相同,若相同则返回true,如果长度不同则直接返回false。所以String类中的equals()比较的是两个字符串对象的内容。
其他的一些类诸如Double,Date,Integer等,都对equals方法进行了重写用来比较指向的对象所存储的内容是否相等。
1)对于==,如果作用于基本数据类型的变量,则直接比较其存储的 “值”是否相等;
如果作用于引用类型的变量,则比较的是所指向的对象的地址
2)对于equals方法,注意:equals方法不能作用于基本数据类型的变量
如果没有对equals方法进行重写,则比较的是引用类型的变量所指向的对象的地址;
诸如String、Date等类对equals方法进行了重写的话,比较的是所指向的对象的内容。
三重写equals()方法与hashCode()方法
我们知道java中的String,Date,Integer等类重写了equals()方法,那么什么时候需要重写equals方法呢?当一个类需要定义属于自己的“逻辑相等”的概念而不仅仅是对象引用是否相等时则需要重写该方法,那么什么时候需要重写hashCode()方法呢?其实hashCode()方法的重写不是强制的,它是为了在将你自己定义的类存入java集合框架然后get出来时确保是同一个对象所做的一种规范性要求,以满足java规范中的相等的对象必须具有相等的散列码,如下面的例子:
class People{
private String name;
private int age;
public People(String name,int age) {
this.name = name;
this.age = age;
}
public void setAge(int age){
this.age = age;
}
@Override
public boolean equals(Object obj) {
// TODO Auto-generated method stub
return this.name.equals(((People)obj).name) && this.age== ((People)obj).age;
}
}
public class Main {
public static void main(String[] args) {
People p1 = new People("Jack", 12);
System.out.println(p1.hashCode());
HashMap<People, Integer> hashMap = new HashMap<People, Integer>();
hashMap.put(p1, 1);
System.out.println(hashMap.get(new People("Jack", 12)));
}
}
在这里我只重写了equals方法,也就说如果两个People对象,如果它的姓名和年龄相等,则认为是同一个人。
这段代码本来的意愿是想这段代码输出结果为“1”,但是事实上它输出的是“null”。为什么呢?原因就在于重写equals方法的同时忘记重写hashCode方法。
虽然通过重写equals方法使得逻辑上姓名和年龄相同的两个对象被判定为相等的对象(跟String类类似),但是要知道默认情况下,hashCode方法是将对象的存储地址进行映射。而java集合get方法时首先会通过hashCode()得到的hash值来判断是否存在该元素,如果hash值不存在,则直接返回null,因此上述代码输出结果为null
HashMap中get(k)源码如下:
public V get(Object key) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) { //通过该hash值与table的长度n-1相与得到数组的索引first
if (first.hash == hash && // always check first node//如果hash值不同下面的代码将不会执行
((k = first.key) == key || (key != null && key.equals(k))))
return first;
if ((e = first.next) != null) {
if (first instanceof TreeNode)//代表该HashMap为数组+红黑树结构
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
do {//否则代表是数组+链表结构
if (e.hash == hash && <span style="font-family: Arial, Helvetica, sans-serif;">//如果hash值不同下面的代码将不会执行</span>
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;//<span style="font-family: Arial, Helvetica, sans-serif;">如果上述红黑树与链表结构中都不存在该hash值则表示该HashMap中不存在该元素,返回null</span>
}
因此如果想上述代码输出结果为“1”,很简单,只需要重写hashCode方法,让equals方法和hashCode方法始终在逻辑上保持一致性。
class People{
private String name;
private int age;
public People(String name,int age) {
this.name = name;
this.age = age;
}
public void setAge(int age){
this.age = age;
}
@Override
public int hashCode() {
// TODO Auto-generated method stub
return name.hashCode()*37+age;
}
@Override
public boolean equals(Object obj) {
// TODO Auto-generated method stub
return this.name.equals(((People)obj).name) && this.age== ((People)obj).age;
}
}
public class Main {
public static void main(String[] args) {
People p1 = new People("Jack", 12);
System.out.println(p1.hashCode());
HashMap<People, Integer> hashMap = new HashMap<People, Integer>();
hashMap.put(p1, 1);
System.out.println(hashMap.get(new People("Jack", 12)));
}
}
这样输出结果即为1与预期结果相同。
四总结:
1hashCode()与equals()都属于Object类中的方法,因此java中的所有的类中都默认存在这两种方法。
2为满足java规范中的相等的对象必须具有相等的hash值,通常在重写equals()时一定要重写hashCode()方法。
3如果没有对equals方法进行重写,则比较的是引用类型的变量所指向的对象的地址;对于==,如果作用于基本数据类型的变量,则直接比较其存储的 “值”是否相等;
如果作用于引用类型的变量,则比较的是所指向的对象的地址。
java中hashCode()与equals()详解的更多相关文章
- java中的io系统详解 - ilibaba的专栏 - 博客频道 - CSDN.NET
java中的io系统详解 - ilibaba的专栏 - 博客频道 - CSDN.NET 亲,“社区之星”已经一周岁了! 社区福利快来领取免费参加MDCC大会机会哦 Tag功能介绍—我们 ...
- Java中的main()方法详解
在Java中,main()方法是Java应用程序的入口方法,也就是说,程序在运行的时候,第一个执行的方法就是main()方法,这个方法和其他的方法有很大的不同,比如方法的名字必须是main,方法必须是 ...
- Java I/O : Java中的进制详解
作者:李强强 上一篇,泥瓦匠基础地讲了下Java I/O : Bit Operation 位运算.这一讲,泥瓦匠带你走进Java中的进制详解. 一.引子 在Java世界里,99%的工作都是处理这高层. ...
- java 中hashcode和equals 总结
一.概述 在Java中hashCode的实现总是伴随着equals,他们是紧密配合的,你要是自己设计了其中一个,就要设计另外一个.当然在多数情况下,这两个方法是不用我们考虑的,直 ...
- java中hashcode()和equals()的详解
今天下午研究了半天hashcode()和equals()方法,终于有了一点点的明白,写下来与大家分享(zhaoxudong 2008.10.23晚21.36). 1. 首先equals()和hashc ...
- java中list和map详解
一.概叙 List , Set, Map都是接口,前两个继承至Collection接口,Map为独立接口, List下有ArrayList,Vector,LinkedList Set下有HashSet ...
- Java中HashCode()和equals()的作用
引言 我们知道Java中的集合(Collection)大致可以分为两类,一类是List,再有一类是Set. 前者集合内的元素是有序的,元素可以重复:后者元素无序,但元素不可重复. 这里就引出一个问题: ...
- hashCode与equals详解
在工作中写业务类通常都会重写hashCode与equals方法,而这两个方法的区别与用途也常常被问道.平时也只是大概知道这二者的用途,今天闲下来,查阅资料加上自己的理 解,总结记录下. hashCod ...
- Java中的枚举使用详解
转载至:http://www.cnblogs.com/linjiqin/archive/2011/02/11/1951632.html package com.ljq.test; /** * 枚举用法 ...
随机推荐
- UVA 3713 Astronauts
The Bandulu Space Agency (BSA) has plans for the following three space missions: • Mission A: Landin ...
- SPOJ Query on a tree V
You are given a tree (an acyclic undirected connected graph) with N nodes. The tree nodes are number ...
- SpringCloud学习之sleuth&zipkin
一.调用链跟踪的必要性 首先我们简单来看一下下单到支付的过程,别的不多说,在业务复杂的时候往往服务会一层接一层的调用,当某一服务环节出现响应缓慢时会影响整个服务的响应速度,由于业务调用层次很“深”,那 ...
- Awesome-Text-Classification:文本分类资源合集
Awesome-Text-Classification https://github.com/fendouai/Awesome-Text-Classification Projects fastTex ...
- 基于pytorch实现HighWay Networks之Highway Networks详解
(一)简述---承接上文---基于pytorch实现HighWay Networks之Train Deep Networks 上文已经介绍过Highway Netwotrks提出的目的就是解决深层神经 ...
- 阿里云服务器Centos 7安装PHP
网上各种别人写的博客 我自己配置了一下php 开始安装的是压缩包 结果php -version 无显示 然后查找各种资料 请教了很多人 需要的环境一一配置了,但是虽然出现了安装成功,但是还是无法查看版 ...
- react 踩的坑
1.如上图所示:没有任何语法错误,可是只要加上</button>闭合标签后就乱套了 解决方案:sublimetext view-syntax-babel-javascript(babel) ...
- IntelliJ IDEA安装配置
1. 从官网安装最新版IntelliJ Idea软件. 2. 激活使用 http://www.3322.cc/soft/37661.html 3. 配置eclipse快捷键 File-->Set ...
- BZOJ#1717:[Usaco2006 Dec]Milk Patterns 产奶的模式(后缀数组+单调队列)
1717: [Usaco2006 Dec]Milk Patterns 产奶的模式 Description 农夫John发现他的奶牛产奶的质量一直在变动.经过细致的调查,他发现:虽然他不能预见明天产奶的 ...
- 41. First Missing Positive(困难, 用到 counting sort 方法)
Given an unsorted integer array, find the first missing positive integer. For example, Given [1,2,0] ...