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; /** * 枚举用法 ...
随机推荐
- 【iOS】苹果开发者账号申请
[1]首先登陆苹果开发者中心:https://developer.apple.com/programs/ 如图有一个按钮enroll,意思是苹果开发者报名(说白了就是要交钱,好让你具备APP测试和上线 ...
- Spring学习笔记3——使用注解的方式完成注入对象中的效果
第一步:修改applicationContext.xml 添加<context:annotation-config/>表示告诉Spring要用注解的方式进行配置 <?xml vers ...
- day4 liaoxuefeng---高级特性
掌握了Python的数据类型.语句和函数,基本上就可以编写出很多有用的程序了. 但是在Python中,代码不是越多越好,而是越少越好.代码不是越复杂越好,而是越简单越好. 基于这一思想,我们来介绍Py ...
- C语言第二次作业-----顺序结构
一:改错题 (1)输出指定信息: 将给定源代码输入编译器: 执行编译命令,发现编译器报错,错误信息如下: 经检查,发现源程序将"stdio.h"误拼为"stido.h&q ...
- Splay讲解
Splay讲解 Splay是平衡树的一种,是一种二叉搜索树,我们先讲解一下它的核心部分. Splay的核心部分就是splay,可能有些人会说什么鬼?这样讲解是不是太不认真了?两个字回答:不是.第一个S ...
- Linux/Centos笔记目录
Linux介绍 Linux入门--个人感想 Google怎么用linux 初入Linux Windows XP硬盘安装Ubuntu 12.04双系统图文详解 实例讲解虚拟机3种网络模式(桥接. ...
- OSX 鼠标和键盘事件
本文转自:http://www.macdev.io/ebook/event.html 事件分发过程 OSX 与用户交互的主要外设是鼠标,键盘.鼠标键盘的活动会产生底层系统事件.这个事件首先传递到IOK ...
- 取list的值
list.get(0):之类的我就不写了 我就写一个我老忘记的 Iterator it = list.iterator(); while(it.hasNext()){ Student stu = it ...
- JavaScript的BOM、DOM操作、节点以及表格(二)
BOM操作 一.什么是BOM BOM(Browser Object Model)即浏览器对象模型. BOM提供了独立于内容 而与浏览器窗口进行交互的对象: BOM由一系列相关的对象构成 ...
- 排序算法的C语言实现(上 比较类排序:插入排序、快速排序与归并排序)
总述:排序是指将元素集合按规定的顺序排列.通常有两种排序方法:升序排列和降序排列.例如,如整数集{6,8,9,5}进行升序排列,结果为{5,6,8,9},对其进行降序排列结果为{9,8,6,5}.虽然 ...