深入理解Java中HashCode方法

关于hashCode,维基百科中:

In the Java programming language, every class implicitly or explicitly
provides a hashCode() method, which digests the data stored in an
instance of the class into a single hash value (a 32-bit signed
integer).

hashCode就是根据存储在一个对象实例中的所有数据,提取出一个32位的整数,该整数的目的是用来标示该实例的唯一性。有点类似于MD5码,每个文件都能通过MD5算法生成一个唯一的MD5码。不过,Java中的hashCode并没有真正的实现为每个对象生成一个唯一的hashCode,还是会有一定的重复几率。

先来看看Object类,我们知道,Object类是java程序中所有类的直接或间接父类,处于类层次的最高点。在Object类里定义了很多我们常见的方法,包括我们要讲的hashCode方法,如下

public final native Class<?> getClass();
public native int hashCode();
public boolean equals(Object obj) {
 return (this == obj);
}
public String toString() {
 return getClass().getName() + "@" + Integer.toHexString(hashCode());
} 

注意到hashCode方法前面有个native的修饰符,这表示hashCode方法是由非java语言实现的,具体的方法实现在外部,返回内存对象的地址。

在java的很多类中都会重写equals和hashCode方法,这是为什么呢?最常见的String类,比如我定义两个字符相同的字符串,那么对它们进行比较时,我想要的结果应该是相等的,如果你不重写equals和hashCode方法,他们肯定是不会相等的,因为两个对象的内存地址不一样。

public int hashCode() {
  int h = hash;
  if (h == 0) {
    int off = offset;
    char val[] = value;
    int len = count; 

      for (int i = 0; i < len; i++) {
        h = 31*h + val[off++];
      }
      hash = h;
    }
    return h;
  } 

其实这段代码是这个数学表达式的实现

s[0]*31^(n-1) + s[1]*31^(n-2) + … + s[n-1]

s[i]是string的第i个字符,n是String的长度。那为什么这里用31,而不是其它数呢?《Effective Java》是这样说的:之所以选择31,是因为它是个奇素数,如果乘数是偶数,并且乘法溢出的话,信息就会丢失,因为与2相乘等价于移位运算。使用素数的好处并不是很明显,但是习惯上都使用素数来计算散列结果。31有个很好的特性,就是用移位和减法来代替乘法,可以得到更好的性能:31*i==(i<<5)-i。现在的VM可以自动完成这种优化。

可以看到,String类是用它的value值作为参数来计算hashCode的,也就是说,相同的value就一定会有相同的hashCode值。这点也很容易理解,因为value值相同,那么用equals比较也是相等的,equals方法比较相等,则hashCode一定相等。反过来不一定成立。它不保证相同的hashCode一定有相同的对象。

一个好的hash函数应该是这样的:为不相同的对象产生不相等的hashCode。

在理想情况下,hash函数应该把集合中不相等的实例均匀分布到所有可能的hashCode上,要想达到这种理想情形是非常困难的,至少java没有达到。因为我们可以看到,hashCode是非随机生成的,它有一定的规律,就是上面的数学等式,我们可以构造一些具有相同hashCode但value值不一样的,比如说:Aa和BB的hashCode是一样的。

如下代码:

public class Main {
  public static void main(String[] args) {
    Main m = new Main();
    System.out.println(m);
    System.out.println(Integer.toHexString(m.hashCode()));
    String a = "Aa";
    String b = "BB";
    System.out.println(a.hashCode());
    System.out.println(b.hashCode());
  }
}

输出结果:

Main@2a139a55
2a139a55
2112
2112

一般在重写equal函数时,也要重写hashCode函数,这是为什么呢?

来看看这个例子,让我们创建一个简单的类Employee

public class Employee
{
  private Integer id;
  private String firstname;
  private String lastName;
  private String department;

  public Integer getId() {
    return id;
  }
  public void setId(Integer id) {
    this.id = id;
  }
  public String getFirstname() {
    return firstname;
  }
  public void setFirstname(String firstname) {
    this.firstname = firstname;
  }
  public String getLastName() {
    return lastName;
  }
  public void setLastName(String lastName) {
    this.lastName = lastName;
  }
  public String getDepartment() {
    return department;
  }
  public void setDepartment(String department) {
    this.department = department;
  }
}

上面的Employee类只是有一些非常基础的属性和getter、setter.现在来考虑一个你需要比较两个employee的情形。

public class EqualsTest {
  public static void main(String[] args) {
    Employee e1 = new Employee();
    Employee e2 = new Employee();

    e1.setId(100);
    e2.setId(100);
    //Prints false in console
    System.out.println(e1.equals(e2));
  }
}

毫无疑问,上面的程序将输出false,但是,事实上上面两个对象代表的是通过一个employee。真正的商业逻辑希望我们返回true。

为了达到这个目的,我们需要重写equals方法。

public boolean equals(Object o) {
    if(o == null)
    {
      return false;
    }
    if (o == this)
    {
      return true;
    }
    if (getClass() != o.getClass())
    {
      return false;
    }
    Employee e = (Employee) o;
    return (this.getId() == e.getId());
}

在上面的类中添加这个方法,EauqlsTest将会输出true。

So are we done?没有,让我们换一种测试方法来看看。

import java.util.HashSet;
import java.util.Set;
public class EqualsTest
{
	public static void main(String[] args)
	  {
		Employee e1 = new Employee();
		Employee e2 = new Employee();
		e1.setId(100);
		e2.setId(100);
		//Prints 'true'
		System.out.println(e1.equals(e2));
		Set<Employee> employees = new HashSet<Employee>();
		employees.add(e1);
		employees.add(e2);
		//Prints two objects
		System.out.println(employees);
	}

上面的程序输出的结果是两个。如果两个employee对象equals返回true,Set中应该只存储一个对象才对,问题在哪里呢?

我们忘掉了第二个重要的方法hashCode()。就像JDK的Javadoc中所说的一样,如果重写equals()方法必须要重写hashCode()方法。我们加上下面这个方法,程序将执行正确。

@Override
 public int hashCode()
 {
  final int PRIME = 31;
  int result = 1;
  result = PRIME * result + getId();
  return result;
 }

需要注意记住的事情

尽量保证使用对象的同一个属性来生成hashCode()和equals()两个方法。在我们的案例中,我们使用员工id。
eqauls方法必须保证一致(如果对象没有被修改,equals应该返回相同的值)
任何时候只要a.equals(b),那么a.hashCode()必须和b.hashCode()相等。
两者必须同时重写。

总结

以上就是本文关于深入理解Java中HashCode方法的全部内容,希望对大家有所帮助。感兴趣的朋友可以继续参阅本站其他相关专题,如有不足之处,欢迎留言指出。感谢朋友们对本站的支持!

(0)

相关推荐

  • java中重写equals()方法的同时要重写hashcode()方法(详解)

    object对象中的 public boolean equals(Object obj),对于任何非空引用值 x 和 y,当且仅当 x 和 y 引用同一个对象时,此方法才返回 true: 注意:当此方法被重写时,通常有必要重写 hashCode 方法,以维护 hashCode 方法的常规协定,该协定声明相等对象必须具有相等的哈希码.如下: (1) 当obj1.equals(obj2)为true时,obj1.hashCode() == obj2.hashCode()必须为true (2) 当obj

  • Java 中HashCode作用_动力节点Java学院整理

    第1 部分 hashCode的作用 Java集合中有两类,一类是List,一类是Set他们之间的区别就在于List集合中的元素师有序的,且可以重复,而Set集合中元素是无序不可重复的.对于List好处理,但是对于Set而言我们要如何来保证元素不重复呢?通过迭代来equals()是否相等.数据量小还可以接受,当我们的数据量大的时候效率可想而知(当然我们可以利用算法进行优化).比如我们向HashSet插入1000数据,难道我们真的要迭代1000次,调用1000次equals()方法吗?hashCod

  • java 中HashCode重复的可能性

    java 中HashCode重复的可能性 今天有同事提议用String的hashcode得到int类型作为主键.其实hashcode重复的可能性超大,下面是java的缺省算法: public int hashCode() { int h = hash; if (h == 0) { int off = offset; char val[] = value; int len = count; for (int i = 0; i < len; i++) { h = 31*h + val[off++];

  • 探索Java中的equals()和hashCode()方法_动力节点Java学院整理

    equals()和hashCode()区别?  equals():反映的是对象或变量具体的值,即两个对象里面包含的值--可能是对象的引用,也可能是值类型的值.  hashCode():计算出对象实例的哈希码,并返回哈希码,又称为散列函数.根类Object的hashCode()方法的计算依赖于对象实例的D(内存地址),故每个Object对象的hashCode都是唯一的:当然,当对象所对应的类重写了hashCode()方法时,结果就截然不同了. 之所以有hashCode方法,是因为在批量的对象比

  • 详解Java中hashCode的作用

    详解Java中hashCode的作用 以下是关于HashCode的官方文档定义: hashcode方法返回该对象的哈希码值.支持该方法是为哈希表提供一些优点,例如,java.util.Hashtable 提供的哈希表. hashCode 的常规协定是: 在 Java 应用程序执行期间,在同一对象上多次调用 hashCode 方法时,必须一致地返回相同的整数,前提是对象上 equals 比较中所用的信息没有被修改.从某一应用程序的一次执行到同一应用程序的另一次执行,该整数无需保持一致. 如果根据

  • Java中的hashcode方法介绍

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

  • java中重写equals和重写hashCode()

    java中重写equals和重写hashCode() 记得在刚上初一的时候,第一堂数学课学的是集合,那时候我知道了集合是不允许重复元素存在的. hashCode 方法用于散列集合的查找,equals 方法用于判断两个对象是否相等. 为什么重写了 equals 方法,还要重写 hashCode 方法? 因为如果只重写了 equals 方法,两个对象 equals 返回了true,但是如果没有重写 hashCode 方法,集合还是会插入元素.这样集合中就出现了重复元素了. 接下来详细分析,以 Has

  • Java 覆盖equals时总要覆盖hashcode

    Java 覆盖equals时总要覆盖hashcode 最近学习java 的基础知识,碰到Java 覆盖equals时总要覆盖hashcode时候有许多疑问,经过和同事直接讨论及上网查询的资料,这里整理下,也好帮助大家理解,代码中有说明. 具体实现代码: package cn.xf.cp.ch02.item9; import java.util.HashMap; import java.util.Map; public class PhoneNumber { private final short

  • 深入理解Java中HashCode方法

    关于hashCode,维基百科中: In the Java programming language, every class implicitly or explicitly provides a hashCode() method, which digests the data stored in an instance of the class into a single hash value (a 32-bit signed integer). hashCode就是根据存储在一个对象实例

  • 关于Java中HashCode方法的深入理解

    1.0前言 最近在学习 Go 语言,Go 语言中有指针对象,一个指针变量指向了一个值的内存地址.学习过 C 语言的猿友应该都知道指针的概念.Go 语言语法与 C 相近,可以说是类 C 的编程语言,所以 Go 语言中有指针也是很正常的.我们可以通过将取地址符&放在一个变量前使用就会得到相应变量的内存地址. package main import "fmt" func main() { var a int= 20 /* 声明实际变量 */ var ip *int /* 声明指针变量

  • java中hashCode方法与equals方法的用法总结

    首先,想要明白hashCode的作用,必须要先知道Java中的集合. 总的来说,Java中的集合(Collection)有两类,一类是List,再有一类是Set. 前者集合内的元素是有序的,元素可以重复:后者元素无序,但元素不可重复. 那么这里就有一个比较严重的问题了:要想保证元素不重复,可两个元素是否重复应该依据什么来判断呢? 这就是Object.equals方法了.但是,如果每增加一个元素就检查一次,那么当元素很多时,后添加到集合中的元素比较的次数就非常多了. 也就是说,如果集合中现在已经有

  • java中hashCode、equals的使用方法教程

    前言 众所周知Java.lang.Object 有一个hashCode()和一个equals()方法,这两个方法在软件设计中扮演着举足轻重的角色.在一些类中重写这两个方法以完成某些重要功能. 1.为什么要用 hashCode()? 集合Set中的元素是无序且不可重复的,那判断两个元素是否重复的依据是什么呢? 有人说:比较对象是否相等当然用Object.equal()了.但是,Set中存在大量对象,后添加到集合Set中的对象元素比较次数会逐渐增多,大大降低了程序运行效率. Java中采用哈希算法(

  • 浅谈Java中hashCode的正确求值方法

    本文研究的主要是Java中hashCode的正确求值方法的相关内容,具体如下. 散列表有一项优化,可以将对象的散列码(hashCode)缓存起来,如果散列码不匹配,就不会检查对象的等同性而直接认为成不同的对象.如果散列码(hashCode)相等,才会检测对象是否相等(equals). 如果对象具有相同的散列码(hashCode),他们会被映射到同一个散列桶中.如果散列表中所有对象的散列码(hashCode)都一样,那么该散列表就会退化为链表(linked list),从而大大降低其查询效率. 一

  • java中join方法的理解与说明详解

    前言: java 中的 join() 方法在多线程中会涉及到,这个方法最初理解起来可能有点抽象,用一两次大概就懂了.简单说就是当前线程等待调用join方法的线程结束才能继续往下执行. 1. 举个例子 如下, MyRunnable 类是实现 Runnable 接口的多线程类,其run() 方法是一个计算,计算值存储在 result 字段,获取计算结果就必须等线程执行完之后调用 getResult() 获取 public class MyRunnable implements Runnable {

  • 细品Java8中hashCode方法的使用

    简介 散列函数(英语:Hash function)又称散列算法.哈希函数,是一种从任何一种数据中创建小的数字"指纹"的方法.散列函数把消息或数据压缩成摘要,使得数据量变小,将数据的格式固定下来. Java语言对hashCode的应用 主要用途 hashcode是Object中的函数,所有类都拥有的一个函数,主要返回每个对象的hash值,主要用于哈希表中,如HashMap.HashTable.HashSet. 在这里需要注意的是,他就是为了在一些对象数组里面存储的时候可以节省空间.(我在

  • 深入理解Java中的HashMap

    一.HashMap的结构图示 ​本文主要说的是jdk1.8版本中的实现.而1.8中HashMap是数组+链表+红黑树实现的,大概如下图所示.后面还是主要介绍Hash Map中主要的一些成员以及方法原理. ​那么上述图示中的结点Node具体类型是什么,源码如下.Node是HashMap的内部类,实现了Map.Entery接口,主要就是存放我们put方法所添加的元素.其中的next就表示这可以构成一个单向链表,这主要是通过链地址法解决发生hash冲突问题.而当桶中的元素个数超过阈值的时候就换转为红黑

  • 深入理解java中的重载和覆盖

    说到java中的重载和覆盖呢,大家都很熟悉了吧,但是呢我今天就要写这个. 本文主题: 一.什么是重载 二.什么是覆盖 三.两者之间的区别 重载(overload): 在一个类中,如果出现了两个或者两个以上的同名函数,只要它们的参数的个数,或者参数的类型不同,即可称之为该函数重载了. 即当函数同名时,只看参数列表.和返回值类型没关系. 重载使用的时候需要注意: 1.在使用重载时只能通过不同的参数样式.例如,不同的参数类型,不同的参数个数,不同的参数顺序. 2.方法的异常类型和数目不会对重载造成影响

随机推荐