java 中为什么重写 equals 后需要重写 hashCode

createh519小时前技术教程1

1. equals 和 hashCode 方法之间的关系

  这两个方法都是 Object 的方法,意味着 若一个对象在没有重写 这两个方法时,都会默认采用 Object 类中的方法实现,它们的关系为:

  1. 如果两个对象通过equals()方法比较相等,那么这两个对象的hashCode一定相同。
  2. 如果两个对象hashCode相同,不能证明两个对象是同一个对象(不一定相等),只能证明两个对象在散列结构中存储在同一个地址(不同对象hashCode相同的情况称为hash冲突)。

2.为什么重写equals 后需要重写 hashCode

  Effective Java 第三版 中 描述为什么重写equals 方法后必须重写hashCode 方法:

每个覆盖了equals方法的类中,必须覆盖hashCode。如果不这么做,就违背了hashCode的通用约定,也就是上面注释中所说的。
进而导致该类无法结合所以与散列的集合一起正常运作,这里指的是HashMap、HashSet、HashTable、ConcurrentHashMap。 

  上面注释 为 Object 类中 hashCode 方法注释:

      If two objects are equal according to the {@code equals(Object)}
     *     method, then calling the {@code hashCode} method on each of
     *     the two objects must produce the same integer result.

  结论:如果重写equals不重写hashCode它与散列集合无法正常工作。

3. 以 HashMap 为例进行论证分析

  查看 hashMap 的 put 方法

 public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

  查看hash 方法的实现:

  static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

  查看 get 方法的实现:

   public V get(Object key) {
        Node e;
        return (e = getNode(hash(key), key)) == null ? null : e.value;
    }

  java中HashMap的数据结构是数组+链表+红黑树;这种数据结构,每个键值对都会被存在相应的地址中,从代码中可以看出HashMap是通过key的hashCode以及自身的容量来决定当前键值的存储索引(桶)的,确定桶的位置后,再进入桶中同时判断hashCode和equals两个方法。那也就是说,如果hashCode不同,那么HashMap就一定会创建一个新的Node键值对象。

  HashMap在put一个键值对时,会先根据键的hashCode和equals方法来同时判断该键在容器中是否已经存在,如果存在则覆盖,反之新建。所以如果我们在重写equals方法时,没有重写hashCode方法,那么hashCode方法还是会默认使用Object提供的原始方法,而Object提供的hashCode方法返回值是不会重复的(也就是说每个对象返回的值都不一样)。所以就会导致每个对象在HashMap中都会是一个新的键。

  反向论证:若一个类中重写了 equals 方法,没有重写hashCode方法;且该类的两个对象具有不同属性但 hashCode 相等,在hashMap 以该对象为键进行存储时,会出现hash冲突现象,但发现该类重写了equals 方法,且通过该类的equals 比较之后也是相等,就会出现 hashMap 中只保存了一个对象,采用get 方法获取时,就会获取到别的对象,从而导致获取对象错乱。

  因此 重写equals 方法必须重写 hashCode 方法,用来保证两个对象通过equals()方法比较相等,那么这两个对象的hashCode一定相同 这一原则;

文章来自
https://www.cnblogs.com/zjdxr-up/p/16177068.html

相关文章

Java—类加载的基本机制和过程

类加载的基本机制和过程运行Java程序,就是执行java这个命令,指定包含main方法的完整类名,以及一个classpath,即类路径。类路径可以有多个,对于直接的class文件,路径是class文件...

SpringBoot自定义自动配置这些知识点你需要了解

前言Spring Boot 是一个快速开发框架,可以简化 Spring 应用程序的开发,其中自定义配置是其中一个非常重要的特性。在 Spring Boot 中,自定义配置允许开发者以自己的方式来配置应...

揭秘JVM双亲委派:Java世界里的“家族传承”如何守护代码安全?

揭秘JVM双亲委派模型:Java世界里的“家族传承”如何守护代码安全?一、什么是双亲委派模型?——Java世界的“家族责任制”在JVM中,双亲委派模型是类加载机制的核心规则。简单来说,它像一个“家族责...

京东大佬问我,java高级技术人员要掌握哪些技术呢?

京东大佬问我,java高级技术人员要掌握哪些技术呢?首先,我得考虑Java高级工程师需要哪些核心技能。基础部分可能包括JVM、多线程、集合框架这些,但高级的话可能需要更深入,比如JVM调优、并发编程的...

java面向对象三大特性:封装、继承、多态——举例说明(转载)

概念封装:封装就是将客观的事物抽象成类,类中存在属于这个类的属性和方法。继承:继承就是把父对象继承过来,这样子类就存在了父类的操作方法,java是单继承,就是只能继承一个父对象。多态:多态就是程序中允...