搞懂JAVAObject中的hashCode()

目录
  • Object中的hashCode()
  • hashCode()和equals()
  • 重写的hashCode()方法
  • 总结

Object中的hashCode()

hashCode方法用来返回对象的哈希值,提供该方法是为了支持哈希表,例如HashMap,HashTable等,在Object类中的代码如下:

 public native int hashCode();

这是一个native声明的本地方法,返回一个int型的整数。由于在Object中,因此每个对象都有一个默认的哈希值。

在openjdk8根路径/hotspot/src/share/vm/runtime路径下的synchronizer.cpp文件中,有生成哈希值的代码:

static inline intptr_t get_next_hash(Thread * Self, oop obj) {
  intptr_t value = 0 ;
  if (hashCode == 0) {
     // 返回随机数
     value = os::random() ;
  } else
  if (hashCode == 1) {
     //用对象的内存地址根据某种算法进行计算
     intptr_t addrBits = cast_from_oop<intptr_t>(obj) >> 3 ;
     value = addrBits ^ (addrBits >> 5) ^ GVars.stwRandom ;
  } else
  if (hashCode == 2) {
     // 始终返回1,用于测试
     value = 1 ;
  } else
  if (hashCode == 3) {
     //从0开始计算哈希值
     value = ++GVars.hcSequence ;
  } else
  if (hashCode == 4) {
     //输出对象的内存地址
     value = cast_from_oop<intptr_t>(obj) ;
  } else {
     // 默认的hashCode生成算法,利用xor-shift算法产生伪随机数
     unsigned t = Self->_hashStateX ;
     t ^= (t << 11) ;
     Self->_hashStateX = Self->_hashStateY ;
     Self->_hashStateY = Self->_hashStateZ ;
     Self->_hashStateZ = Self->_hashStateW ;
     unsigned v = Self->_hashStateW ;
     v = (v ^ (v >> 19)) ^ (t ^ (t >> 8)) ;
     Self->_hashStateW = v ;
     value = v ;
  }

  value &= markOopDesc::hash_mask;
  if (value == 0) value = 0xBAD ;
  assert (value != markOopDesc::no_hash, "invariant") ;
  TEVENT (hashCode: GENERATE) ;
  return value;
}

源码中的hashCode其实是JVM启动的一个参数,每一个分支对应一个生成策略,通过-XX:hashCode可以切换hashCode的生成策略。

下面验证第2种生成策略,用软件idea输入参数-XX:hashCode=2,可以看到输出结果正是1,从而进一步验证了上面的源码。

hashCode()和equals()

hashCode()和equals()用来标识对象,两个方法协同工作用来判断两个对象是否相等。对象通过调用 Object.hashCode()生成哈希值,由于不可避免地会存在哈希值冲突的情况 因此hashCode 相同时 还需要再调用 equals 进行一次值的比较,但是若hashCode不同,将直接判定两个对象不同,跳过 equals ,这加快了冲突处理效率。 Object 类定义中对 hashCode和 equals 要求如下:

  • 如果两个对象的equals的结果是相等的,则两个对象的 hashCode 的返回结果也必须是相同的。
  • 任何时候重写equals,都必须同时重写hashCode。

下面看一个小例子:

import java.util.HashMap;
import java.util.Objects;

/**
 * @author mazouri
 * @create 2021-08-10 23:59
 */
public class Person {
    //用户Id,唯一确定用户
    private String id;
    private String name;

    public Person(String id, String name) {
        this.id = id;
        this.name = name;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Person)) return false;
        Person person = (Person) o;
        return Objects.equals(id, person.id) && Objects.equals(name, person.name);
    }

    public static void main(String[] args) {
        HashMap<Person, Integer> map = new HashMap<>();
        //key:Person类型  value:Integer类型
        map.put(new Person("1","张三"),100);
        System.out.println(map.get(new Person("1", "张三")));
    }
}

我们将Person类的实例作为key,value为这个对象的考试成绩。我们期望通过map.get(new Person("1", "张三"))获取该对象的考试成绩,但上面代码的输出结果为null。原因就在于Person类中没有覆盖hashCode方法,从而导致两个相等的实例具有不同的哈希值。HashMap中get()的核心代码如下

if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
     return first;

if条件表达式中的e.hash == hash是先决条件,只有相等才会执行&&后面的代码。equals不相等时并不强制要求哈希值相等,但是一个优秀的哈希算法应尽可能让元素均匀分布,降低冲突发生的概率,即在equals不相等时尽量让哈希值也不相等,这样&&或||短路操作一旦生效,会极大提高程序的效率。像上面的例子,因为没有重写hashCode方法,两个对象有两个哈希值,获取对象时可能在别的哈希桶中查找,即使凑巧在一个哈希桶,因为哈希值不一样,也找不到原来那一个对象。
你可以根据你自己的需求设计重写hashCode方法,或者调用JDK提供好的,比如

  @Override
    public int hashCode() {
        return Objects.hash(id, name);
    }

这样就能解决问题,但是这个运行速度慢一些,因为它们会引发数组的创建,以便传入数目可变的参数, 如果参数中有基本类型,还需要装箱和拆箱 ,建议只将这类散列函数用于不太注重性能的情况。

重写的hashCode()方法

Java为许多常用的数据类型重写了hashCode()方法,比如String,Integer,Double等。比如在Integer类中哈希值就是其int类型的数据。

  public static int hashCode(int value) {
        return value;
    }

总结

本篇文章就到这里了,希望能给你带来帮助,也希望您能够多多关注我们的更多内容!

(0)

相关推荐

  • JSONObject按put顺序排放与输出方式

    JSONObject按put顺序排放与输出 JSONObject put数据之后,排序会发生变化 例如 JSONObject object=new JSONObject(); object.put("aaa",111); object.put("bbb",222); object.put("ccc",333); object.put("ddd",444); 输出结果可能为 {"aaa":111,"

  • java 实现将Object类型转换为int类型

    如何将Object类型转换为int类型 Object object = null; try { Integer.parseInt(object.toString()); } catch (NumberFormatException e) {} 也可以先判定一下是否是Integer //可以先判定一下是否是Integer Object object = "111"; if (object instanceof Integer) { Integer.parseInt(object.toSt

  • Java基础之浅谈hashCode()和equals()

    写在前面 其实很早我就注意到阿里巴巴Java开发规范有一句话:只要重写 equals,就必须重写 hashCode. 我想很多人都会问为什么,所谓知其然知其所以然,对待知识不单止知道结论还得知道原因. hashCode方法 hashCode()方法的作用是获取哈希码,返回的是一个int整数 学过数据结构的都知道,哈希码的作用是确定对象在哈希表的索引下标.比如HashSet和HashMap就是使用了hashCode方法确定索引下标.如果两个对象返回的hashCode相同,就被称为"哈希冲突&quo

  • 一文告诉你为什么要重写hashCode()方法和equals()方法

    首先我们看下object源码中如何定义hashcode与equals方法的 public native int hashCode(); public boolean equals(Object obj) { return (this == obj); } Object类中的hashCode()方法,用的是native关键字修饰,说明这个方法是个原生函数,也就说这个方法的实现不是用java语言实现的,是使用c/c++实现的,并且被编译成了DLL,由java去调用,jdk源码中不包含. Java将调

  • 深入了解Java核心类库--Objects类

    目录 1 Objects 1.1 Objects方法 1.2 Objects常用方法 1.2.1 equals(Object a, Object b) 1.2.2 isNull(Object obj) 1.2.3 nonNull(Object obj) 1.2.4 requireNonNull(T obj) 1.3 Objects源码 2 区别于Object 2.1 Object构造方法 2.2 Object其他方法 2.2.1 equals(Object obj) 2.2.2 toString

  • 搞懂JAVAObject中的hashCode()

    目录 Object中的hashCode() hashCode()和equals() 重写的hashCode()方法 总结 Object中的hashCode() hashCode方法用来返回对象的哈希值,提供该方法是为了支持哈希表,例如HashMap,HashTable等,在Object类中的代码如下: public native int hashCode(); 这是一个native声明的本地方法,返回一个int型的整数.由于在Object中,因此每个对象都有一个默认的哈希值. 在openjdk8

  • 一文搞懂Java中的反射机制

    前一段时间一直忙,所以没什么时间写博客,拖了这么久,也该更新更新了.最近看到各种知识付费的推出,感觉是好事,也是坏事,好事是对知识沉淀的认可与推动,坏事是感觉很多人忙于把自己的知识变现,相对的在沉淀上做的实际还不够,我对此暂时还没有什么想法,总觉得,慢慢来,会更快一点,自己掌握好节奏就好. 好了,言归正传. 反射机制是Java中的一个很强大的特性,可以在运行时获取类的信息,比如说类的父类,接口,全部方法名及参数,全部常量和变量,可以说类在反射面前已经衣不遮体了(咳咳,这是正规车).先举一个小栗子

  • 一文搞懂Spring中的注解与反射

    目录 前言 一.内置(常用)注解 1.1@Overrode 1.2@RequestMapping 1.3@RequestBody 1.4@GetMapping 1.5@PathVariable 1.6@RequestParam 1.7@ComponentScan 1.8@Component 1.9@Service 1.10@Repository 二.元注解 @Target @Retention @Documented @Inherited 三.自定义注解 四.反射机制概述 4.1动态语言与静态语

  • 一文搞懂Java中对象池的实现

    目录 1. 什么是对象池 2. 为什么需要对象池 3. 对象池的实现 4. 开源的对象池工具 5. JedisPool 对象池实现分析 6. 对象池总结 最近在分析一个应用中的某个接口的耗时情况时,发现一个看起来极其普通的对象创建操作,竟然每次需要消耗 8ms 左右时间,分析后发现这个对象可以通过对象池模式进行优化,优化后此步耗时仅有 0.01ms,这篇文章介绍对象池相关知识. 1. 什么是对象池 池化并不是什么新鲜的技术,它更像一种软件设计模式,主要功能是缓存一组已经初始化的对象,以供随时可以

  • 一文带你搞懂Java中Object类和抽象类

    目录 一.抽象类是什么 二.初始抽象类 2.1 基本语法 2.2 继承抽象类 三.抽象类总结 四.Object类 4.1 初始Object 4.2 toString 4.3 equals 4.4 hashcode 一.抽象类是什么 在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类. 由于抽象类不能实例化对象,所以抽象类必须被继承,才能被使用.也是因为这个原因,通常在设计阶段决定要

  • 彻底搞懂JavaScript中的apply和call方法(必看)

    call和apply都是为了改变某个函数运行的context上下文而存在的,即为了改变函数体内部this的指向.因为JavaScript的函数存在定义上下文和运行时上下文以及上下文是可以改变的概念. 回到目录定义 fun.apply(thisArg, [argsArray]) fun.call(thisArg, arg1,arg2, ...) 其中thisArg可以为null或undefined,此时表示全局对象,更详细见MDN:apply().call() 二者的作用完全一样,只是接受参数的方

  • 一篇文章轻松搞懂Java中的自旋锁

    前言 锁作为并发共享数据,保证一致性的工具,在JAVA平台有多种实现(如 synchronized 和 ReentrantLock等等 ) .这些已经写好提供的锁为我们开发提供了便利. 在之前的文章<一文彻底搞懂面试中常问的各种"锁" >中介绍了Java中的各种"锁",可能对于不是很了解这些概念的同学来说会觉得有点绕,所以我决定拆分出来,逐步详细的介绍一下这些锁的来龙去脉,那么这篇文章就先来会一会"自旋锁". 正文 出现原因 在我们的

  • 一文搞懂ES6中的Map和Set

    Map Map对象保存键值对.任何值(对象或者原始值) 都可以作为一个键或一个值.构造函数Map可以接受一个数组作为参数. Map和Object的区别 •一个Object 的键只能是字符串或者 Symbols,但一个Map 的键可以是任意值. •Map中的键值是有序的(FIFO 原则),而添加到对象中的键则不是. •Map的键值对个数可以从 size 属性获取,而 Object 的键值对个数只能手动计算. •Object 都有自己的原型,原型链上的键名有可能和你自己在对象上的设置的键名产生冲突.

  • 详解帮你彻底搞懂JS中的prototype、__proto__与constructor(图解)

    提示:不要排斥,静下心来,认真读完,你就搞懂了!(可以先看一下最后的总结部分再回过头来完整看完) 1. 前言 作为一名前端工程师,必须搞懂JS中的prototype.__proto__与constructor属性,相信很多初学者对这些属性存在许多困惑,容易把它们混淆,本文旨在帮助大家理清它们之间的关系并彻底搞懂它们.这里说明一点,__proto__属性的两边是各由两个下划线构成(这里为了方便大家看清,在两下划线之间加入了一个空格:_ _proto_ _),实际上,该属性在ES标准定义中的名字应该

  • 帮你彻底搞懂JS中的prototype、__proto__与constructor(图解)

    提示:不要排斥,静下心来,认真读完,你就搞懂了!(可以先看一下最后的总结部分再回过头来完整看完) 1. 前言 作为一名前端工程师,必须搞懂JS中的prototype.__proto__与constructor属性,相信很多初学者对这些属性存在许多困惑,容易把它们混淆,本文旨在帮助大家理清它们之间的关系并彻底搞懂它们.这里说明一点,__proto__属性的两边是各由两个下划线构成(这里为了方便大家看清,在两下划线之间加入了一个空格:_ _proto_ _),实际上,该属性在ES标准定义中的名字应该

随机推荐