理解Java中的hashCode和equals 方法

在Java里面所有的类都直接或者间接的继承了java.lang.Object类,Object类里面提供了11个方法,如下:

Java代码  

  1. ````
  2. 1,clone()
  3. 2,equals(Object obj)
  4. 3,finalize()
  5. 4,getClass()
  6. 5,hashCode()
  7. 6,notify()
  8. 7,notifyAll()
  9. 8,toString()
  10. 9,wait()
  11. 10,wait(long timeout)
  12. 11,wait(long timeout, int nanos)
  13. ````

这里面我们常用的方法有三个:

Java代码  

  1. ````
  2. toString()
  3. equals(Object obj)
  4. hashCode()
  5. ````

toString方法,相信用过Java的人都不会陌生,默认打印的是:类名@十六进制的hashCode,源码中定义如下:

Java代码  

  1. ````
  2. public String toString() {
  3. return getClass().getName() + "@" + Integer.toHexString(hashCode());
  4. }
  5. ````

在经过重写后,我们可以打印一个class的所有属性,这样在打印log或调试时比较方便。

下面重点介绍下hashCode和equals方法:

(1)equals方法,在JDK默认的情况下比较的是对象的内存地址,源码如下:

Java代码  

  1. ````
  2. public boolean equals(Object obj) {
  3. return (this == obj);
  4. }
  5. ````

(2)hashcode方法,默认情况下返回的是一个唯一的整数,代表该实例的内存地址,注意这个数字 
并不是实际的内存地址,Java是没办法直接获取内存地址的,必须得由C或者C++获取,所以这个方法是用 
native修饰的

Java代码  

  1. ````
  2. public native int hashCode();
  3. ````

由于默认情况下,equals方法比较的是内存地址,而在实际开发中,我们判断两个对象是否相等,一般都是根据对象的属性来判断的, 
所以需要重写这个方法,不然的话,是没办法比较的。举例如下:

定义的类如下:

Java代码  

  1. ````
  2. public class Hero {
  3. private String id;
  4. private String name;
  5. public Hero() {
  6. }
  7. public Hero(String id, String name) {
  8. this.id = id;
  9. this.name = name;
  10. }
  11. public String getId() {
  12. return id;
  13. }
  14. public void setId(String id) {
  15. this.id = id;
  16. }
  17. public String getName() {
  18. return name;
  19. }
  20. public void setName(String name) {
  21. this.name = name;
  22. }
  23. }
  24. ````

直接比较两个对象,结果是不相等的:

Java代码  

  1. ````
  2. ``````  Hero h1=new Hero("1","张飞");
  3. Hero h2=new Hero("1","张飞");
  4. //false
  5. System.out.println(h1.equals(h2));
  6. ````

因为他们的内存地址是不同的,所以结果是false,如果我们想要认为他是相等的,那么就需要重写 
equals方法:

Java代码  

  1. ````
  2. @Override
  3. public boolean equals(Object o) {
  4. if (this == o) return true;//如果内存地址相等,则两个对象必定相等
  5. if (o == null || getClass() != o.getClass()) return false;//如果有一个为null或者class不一样,认为必不相等
  6. Hero hero = (Hero) o;//
  7. if (id != null ? !id.equals(hero.id) : hero.id != null) return false;//比较id,不相等就返回false
  8. return name != null ? name.equals(hero.name) : hero.name == null;//上面的条件通过后比较name,如果相等返回true,不相等返回false
  9. }
  10. ````

在重写equals方法后,我们在比较两个对象,发现就相等了

Java代码  

  1. ````
  2. ```     Hero h1=new Hero("1","张飞");
  3. Hero h2=new Hero("1","张飞");
  4. //true
  5. System.out.println(h1.equals(h2));
  6. ````

接着我们看第二个例子,将其放入ArrayList中,然后判断是否存在,发现也生效了:

Java代码  

  1. ````
  2. `        Hero h1=new Hero("1","张飞");
  3. List<Hero> heros=new ArrayList<Hero>();
  4. heros.add(h1);
  5. //true
  6. System.out.println(heros.contains(new Hero("1","张飞")));
  7. ````

到目前为止,我们还没有对hashCode进行操作,那么大家可能会有一个疑问,既然都有equals方法比较了,为啥还需要hashCode方法呢? 别着急,继续看下面的例子:

我们都知道在Java里面HashSet类,去无序去重的,下面看一下,只重写equasl方法能不能实现对class的去重:

Java代码  

  1. ````
  2. `        Hero h1=new Hero("1","张飞");
  3. Hero h2=new Hero("1","张飞");
  4. Set<Hero> heros=new HashSet<Hero>();
  5. heros.add(h1);
  6. heros.add(h2);
  7. //2
  8. System.out.println(heros.size());
  9. //false
  10. System.out.println(heros.contains(new Hero("1","张飞")));
  11. ````

从上面的结果看,并没有去重,有的小伙伴会说为啥时string类型的时候就能去重?这是因为Stirng类默认已经重写了equals和hashcode方法,当然所有的基本类型都重写这两个方法了。

接着回到上面的问题,为什么在HashSet中去重失效了呢?

其实,不止是HashSet,在HashMap和Hashtable等等所有使用hash相关的数据结构中,如果使用时不重写hashcode,那么就没法比较对象是否存在。

这其实与HashMap存储原理相关(HashSet底层用的也是HashMap),HashMap在存储时其实是采用了数组+链表的存储结构,数组 
中的每一个元素,我们可以理解成是一个buckets(桶),桶里面的结构是链表,而数据是如何分到各个桶里面其实与hashCode有很大关系,只有hashCode一样的 
对象才能被分到一个桶里。存的时候,遍历链表判断是否存在,如果存在就不覆盖,如果不存在就把该元素放在链表的头,把next指向上一次的头,而读取的时候先定位到桶里,然后遍历 
链表找到该元素即可。

理解了这些,就明白了为啥上面的例子中,去重失效了。就是因为他们的hashCode不一样,导致被分到不同的桶里面了,自然就没法去重了。

重写hashCode之后,再看结果:

Java代码  

  1. ````
  2. @Override
  3. public int hashCode() {
  4. return  Integer.parseInt(id);
  5. }
  6. ````

Java代码  

  1. ````
  2. ·        Hero h1=new Hero("1","张飞");
  3. Hero h2=new Hero("1","张飞");
  4. Set<Hero> heros=new HashSet<Hero>();
  5. heros.add(h1);
  6. heros.add(h2);
  7. //1
  8. System.out.println(heros.size());
  9. //true
  10. System.out.println(heros.contains(new Hero("1","张飞")));
  11. ````

这下结果就对了。

那么问题来了,为啥需要hashCode? 因为在HashSet中,可以存储大量的元素,如果没有hashCode,那么每次就得全量的比较每一个元素,来判断 
是否存在,这样以来效率肯定极低,而有了hashCode之后,只需要找到该数据的链表,然后遍历这个链表的数据即可,这样以来效率 
就大大提升。

一个Java交流平台分享给你们,让你在实践中积累经验掌握原理。如果你想拿高薪,想突破瓶颈,想跟别人竞争能取得优势的,想进BAT但是有担心面试不过的,可以加Java学习交流群:642830685

注:加群要求

1、大学学习的是Java相关专业,毕业后面试受挫,找不到对口工作

2、在公司待久了,现在过得很安逸,但跳槽时面试碰壁。需要在短时间内进修、跳槽拿高薪的

3、参加过线下培训后,知识点掌握不够深刻,就业困难,想继续深造

4、已经在Java相关部门上班的在职人员,对自身职业规划不清晰,混日子的

5、有一定的C语言基础,接触过java开发,想转行的

总结:

(1)如果两个对象相等,那么他们必定有相同的hashcode

(2)如果两个对象的hashcode相等,他们却不一定相等

(3)重写equasl方法时,一定要记得重写hashcode方法,尤其用在hash类的数据结构中。

原文地址:http://blog.51cto.com/13551455/2066791

时间: 2024-08-01 13:16:07

理解Java中的hashCode和equals 方法的相关文章

Java中的“==操作符”和equals方法有什么区别

Java中的"=="和equals方法究竟有什么区别? 1.==操作符 "=="操作符专门用来比较两个变量的值是否相等,也就是用于比较变量所对应的内存中所存储的数值是否相同,要比较两个基本类型的数据或两个引用变量是否相等,只能用==操作符. 如果一个变量指向的数据是对象类型的,那么,这时候涉及了两块内存,对象本身占用一块内存(堆内存),变量也占用一块内存,例如: Objet obj = new Object(); 变量obj是一个内存,new Object()是另一

HashMap中使用自定义类作为Key时,为何要重写HashCode和Equals方法

之前一直不是很理解为什么要重写HashCode和Equals方法,才只能作为键值存储在HashMap中.通过下文,可以一探究竟. 首先,如果我们直接用以下的Person类作为键,存入HashMap中,会发生发生什么情况呢? public class Person { private String id; public Person(String id) { this.id = id; } } import java.util.HashMap; public class Main { public

有关java中的hashCode问题

1. HashSet集合存储数据的结构(哈希表) 1.1 什么是哈希表? 哈希表底层使用的也是数组机制,数组中也存放对象,而这些对象往数组中存放时的位置比较特殊,当需要把这些对象给数组中存放时,那么会根据这些对象的特有数据结合相应的算法,计算出这个对象在数组中的位置,然后把这个对象存放在数组中.而这样的数组就称为哈希数组,即就是哈希表. 1.2 哈希表存储数据结构原理 当向哈希表中存放元素时,需要根据元素的特有数据结合相应的算法,这个算法其实就是Object类中的hashCode方法.由于任何对

Java 中正确使用 hashCode 和 equals 方法

在这篇文章中,我将告诉大家我对hashCode和equals方法的理解.我将讨论他们的默认实现,以及如何正确的重写他们.我也将使用Apache Commons提供的工具包做一个实现. 目录: hashCode()和equals()的用法 重写默认实现 使用Apache Commons Lang包重写hashCode()和equals() 需要注意记住的事情 当使用ORM的时候特别要注意的 hashCode()和equals()定义在Object类中,这个类是所有java类的基类,所以所有的jav

浅谈Java中的hashCode方法

哈希表这个数据结构想必大多数人都不陌生,而且在很多地方都会利用到hash表来提高查找效率.在Java的Object类中有一个方法: public native int hashCode(); 根据这个方法的声明可知,该方法返回一个int类型的数值,并且是本地方法,因此在Object类中并没有给出具体的实现. 为何Object类需要这样一个方法?它有什么作用呢?今天我们就来具体探讨一下hashCode方法. 一.hashCode方法的作用 对于包含容器类型的程序设计语言来说,基本上都会涉及到has

浅谈Java中的hashcode方法 - 海 子

浅谈Java中的hashcode方法 哈希表这个数据结构想必大多数人都不陌生,而且在很多地方都会利用到hash表来提高查找效率.在Java的Object类中有一个方法: public native int hashCode(); 根据这个方法的声明可知,该方法返回一个int类型的数值,并且是本地方法,因此在Object类中并没有给出具体的实现. 为何Object类需要这样一个方法?它有什么作用呢?今天我们就来具体探讨一下hashCode方法. 一.hashCode方法的作用 对于包含容器类型的程

浅谈JAVA中的“hashcode()”方法

浅谈Java中的hashcode方法 哈希表这个数据结构想必大多数人都不陌生,而且在很多地方都会利用到hash表来提高查找效率.在Java的Object类中有一个方法: public native int hashCode(); 为何Object类需要这样一个方法?它有什么作用呢?今天我们就来具体探讨一下hashCode方法. 根据这个方法的声明可知,该方法返回一个int类型的数值,并且是本地方法,因此在Object类中并没有给出具体的实现. 一.hashCode方法的作用 对于包含容器类型的程

浅谈Java中的hashcode方法(转载)

哈希表这个数据结构想必大多数人都不陌生,而且在很多地方都会利用到hash表来提高查找效率.在Java的Object类中有一个方法: 1 public native int hashCode(); 根据这个方法的声明可知,该方法返回一个int类型的数值,并且是本地方法,因此在Object类中并没有给出具体的实现. 为何Object类需要这样一个方法?它有什么作用呢?今天我们就来具体探讨一下hashCode方法. 一.hashCode方法的作用 对于包含容器类型的程序设计语言来说,基本上都会涉及到h

Java中的hashcode方法

一.hashCode方法的作用 对于包含容器类型的程序设计语言来说,基本上都会涉及到hashCode.在Java中也一样,hashCode方法的主要作用是为了配合基于散列的集合一起正常运行,这样的散列集合包括HashSet.HashMap以及HashTable. 为什么这么说呢?考虑一种情况,当向集合中插入对象时,如何判别在集合中是否已经存在该对象了?(注意:集合中不允许重复的元素存在) 也许大多数人都会想到调用equals方法来逐个进行比较,这个方法确实可行.但是如果集合中已经存在一万条数据或