​ 网络上关于Java中对象的地址值和对象hashCode方法返回值之间的关系众说纷纭,搞得人一头雾水,随遍寻网络,发现一好文。故征得博主同意,稍作修饰,请至本博。

原文链接:Java 对象的内存地址是否就是hash code

0. 先说结论

结论hashCode方法的返回值对象的地址值不是一回事

​ 使用对象的地址值生成其hashCode返回值(即对象的hash值)只是其众多生成策略之一。

​ hashCode()返回的对象hash值可以理解为该对象的标识,就像ID一样(还是一个可能会重复的ID)

一、查询hashCode的API

1. API文档

​ 先从Object类的hashCode()方法入手,JDK1.6版本API解释如下:

blog-20200410-01-hashcode_api

​ 其中关于hashCode()方法的规定有三个,并在最后强调“Java编程语言中不需要这种实现技巧”。

2. JDK源码

/**
    * Returns a hash code value for the object. This method is
    * supported for the benefit of hash tables such as those provided by
    * {@link java.util.HashMap}.
    * <p>
    * The general contract of {@code hashCode} is:
    * <ul>
    * <li>Whenever it is invoked on the same object more than once during
    *     an execution of a Java application, the {@code hashCode} method
    *     must consistently return the same integer, provided no information
    *     used in {@code equals} comparisons on the object is modified.
    *     This integer need not remain consistent from one execution of an
    *     application to another execution of the same application.
    * <li>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.
    * <li>It is <em>not</em> required that if two objects are unequal
    *     according to the {@link java.lang.Object#equals(java.lang.Object)}
    *     method, then calling the {@code hashCode} method on each of the
    *     two objects must produce distinct integer results.  However, the
    *     programmer should be aware that producing distinct integer results
    *     for unequal objects may improve the performance of hash tables.
    * </ul>
    * <p>
    * As much as is reasonably practical, the hashCode method defined
    * by class {@code Object} does return distinct integers for
    * distinct objects. (The hashCode may or may not be implemented
    * as some function of an object's memory address at some point
    * in time.)
    *
    * @return  a hash code value for this object.
    * @see     java.lang.Object#equals(java.lang.Object)
    * @see     java.lang.System#identityHashCode
    */
@HotSpotIntrinsicCandidate
public native int hashCode();

3. 初步结论

​ 它是被native关键字修饰,也就是说具体的实现不是由Java 来实现的,Java 只负责调用!
​ 再有就是注释说:

//As much as is reasonably practical… some function of an object’s memory address at some point in time)

​ 翻译一下

//从最合乎逻辑、切合实际的角度来看,在不同类定义的hashCode() 方法就是应该给不同的对象返回不同的hash code。(Hash code 也许是或者不是通过对象的内存地址来实现。)

二、网上检索

​ 其实,有大神自己用Open JDK 去查找hashCode() 的底层实现,可以参照以下文章:

Java Object.hashCode()返回的是对象内存地址?

​ 文章中得出的结论:

  1. Hash code 有5中不同的生成策略:

        /*
        1. 返回一个Park-Miller伪随机数生成器生成的随机数。
        2. 返回将对象的内存地址做移位运算后与一个随机数进行异或得到结果。
        3. 返回1。
        4. 返回一个自增序列的当前值。
        5. 返回当前对象的内存地址。
        6. 返回由当前线程有关的一个随机数和三个确定值,经Marsaglia’s xorshift scheme随机数算法得到的一个随机数。
        */
        ```

所以,跟内存相关的生成方式只有1种,只是JVM 默认选择6)。

三、验证

​ 大胆猜想,小心验证。接下来验证一下。

1. 通过设置JVM 启动参数来输出对象的内存地址

​ 是的,我们可以设置JVM 的启动参数来输出对象的内存地址,但是我们需要通过什么来查看呢?没错,hashCcode。

  1. 参数 & 值:

    1. -XX:+UnlockExperimentalVMOptions
    2. -XX:hashCode=4

    注:4代表上述引用文章中,hashcode 生成策略的第5项策略。

  2. 在IDEA 中的测试:

    1. 按照1中列出,设置JVM 的启动参数。
      Run -> Edit Configurations -> VM Options
      如下图:

      blog-20200410-01-vm-args

  3. 写DEMO 代码。
    注:本人安装过JOL (Java Object Layout) 插件,所以输出的结果包含通过插件的输出的结果。下载JetBrains: JOL Java Object Layout

    import java.util.HashSet;
    import java.util.Set;
    
    import org.openjdk.jol.info.ClassLayout;
    import org.openjdk.jol.info.GraphLayout;
    
    public class HashCodeDemo {
      private int i;
    
      public int getI() {
          return i;
      }
    
      public void setI(int i) {
          this.i = i;
      }
    
      public static void main(String[] args) {
          HashCodeDemo a = new HashCodeDemo();
          HashCodeDemo b = new HashCodeDemo();
          a.setI(1);
          b.setI(1);
    
          Set<HashCodeDemo> set = new HashSet<HashCodeDemo>();
          set.add(a);
          set.add(b);
    
          System.out.println(System.getProperty("java.vm.name"));
          System.out.println(System.getProperty("java.vm.version"));
    
          System.out.println("a.hashCode():" + a.hashCode());
          System.out.println("a.hashCode():0x" + Integer.toHexString(a.hashCode()));
          System.out.println(ClassLayout.parseInstance(a).toPrintable());
          System.out.println(GraphLayout.parseInstance(a).toPrintable());
          System.out.println("size : " + GraphLayout.parseInstance(a).totalSize());
          System.out.println("----------------------");
          System.out.println("b.hashCode():" + b.hashCode());
          System.out.println("b.hashCode():0x" + Integer.toHexString(b.hashCode()));
          System.out.println(ClassLayout.parseInstance(b).toPrintable());
          System.out.println(GraphLayout.parseInstance(b).toPrintable());
          System.out.println("size : " + GraphLayout.parseInstance(b).totalSize());
      }
    }

4. 结果

blog-20200410-01-output-arg1

5. 分析对比

从测试结果来看,我们不难发现,对象a 和b 的hashcode 就是他们的自身的内存地址,可以从对象头、ADDRESS 列的值观察。

当我们将JVM 的启动参数复原,即删除VM options 里的值:然后结果如下:

blog-20200410-01-output-arg2

6. 结论,如博文开头。

有帮到你吗?有用点一下哈|´・ω・)ノ