hashcode和equals方法

本文深入探讨了Java中对象的hashcode方法和equals方法的区别与联系,解释了hashcode并非简单返回内存地址,而是存在对象头中。文章还讨论了在重写equals方法时为什么需要重写hashcode方法,特别是在使用容器时的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

对象的hashcode方法和equals方法如果没有重写都是Object类的方法,因为所有对象都继承自Object。

所以我们看看Object类这两个方法的源码:

equals方法源码:

    public boolean equals(Object obj) {
        return (this == obj);
    }

hashcode方法源码:

public native int hashCode();

可以看到是一个native方法,也就是用C++写的,那C++这个方法到底长啥样?

来看看它的源码:

static inline intptr_t get_next_hash(Thread * Self, oop obj) {
  intptr_t value = 0 ;
  if (hashCode == 0) {
     // This form uses an unguarded global Park-Miller RNG,
     // so it's possible for two threads to race and generate the same RNG.
     // On MP system we'll have lots of RW access to a global, so the
     // mechanism induces lots of coherency traffic.

     //此表格使用无人看守的全球Park-Miller RNG,
     //所以两个线程可能会竞争并生成相同的RNG。
     //在MP系统上,我们会有很多对全局的RW访问,所以
     //机制引发了大量的一致性流量。
     value = os::random() ;
  } else
  if (hashCode == 1) {
     // This variation has the property of being stable (idempotent)
     // between STW operations.  This can be useful in some of the 1-0
     // synchronization schemes.

     //这种变化具有稳定的特性(幂等)
     //在STW操作之间。这在1-0的一些中很有用
     //同步方案
     intptr_t addrBits = intptr_t(obj) >> 3 ;
     value = addrBits ^ (addrBits >> 5) ^ GVars.stwRandom ;
  } else
  if (hashCode == 2) {
     value = 1 ;            // for sensitivity testing
  } else
  if (hashCode == 3) {
     value = ++GVars.hcSequence ;
  } else
  if (hashCode == 4) {
     value = intptr_t(obj) ;
  } else {
     // Marsaglia's xor-shift scheme with thread-specific state
     // This is probably the best overall implementation -- we'll
     // likely make this the default in future releases.

     // Marsaglia的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;
}
//
intptr_t ObjectSynchronizer::FastHashCode (Thread * Self, oop obj) {
  if (UseBiasedLocking) {

    //注意:整个JVM中的许多地方都不期望安全点
    //被带到这里,特别是关于perm gen的大多数操作
    //对象但是,我们只偏向Java实例和所有
    //可能撤销偏见的identity_hash的调用站点
    //经过检查以确保他们可以处理安全点。该
    //添加了偏置模式的检查是为了避免无用的调用
    //线程本地存储。
    if (obj->mark()->has_bias_pattern()) {
      // Box and unbox the raw reference just in case we cause a STW safepoint.
      Handle hobj (Self, obj) ;
      // Relaxing assertion for bug 6320749.
      assert (Universe::verify_in_progress() ||
              !SafepointSynchronize::is_at_safepoint(),
             "biases should not be seen by VM thread here");
      BiasedLocking::revoke_and_rebias(hobj, false, JavaThread::current());
      obj = hobj() ;
      assert(!obj->mark()->has_bias_pattern(), "biases should be revoked by now");
    }
  }

  // hashCode() is a heap mutator ...
  // Relaxing assertion for bug 6320749.
  assert (Universe::verify_in_progress() ||
          !SafepointSynchronize::is_at_safepoint(), "invariant") ;
  assert (Universe::verify_in_progress() ||
          Self->is_Java_thread() , "invariant") ;
  assert (Universe::verify_in_progress() ||
         ((JavaThread *)Self)->thread_state() != _thread_blocked, "invariant") ;

  ObjectMonitor* monitor = NULL;
  markOop temp, test;
  intptr_t hash;
  markOop mark = ReadStableMark (obj);

  // object should remain ineligible for biased locking
  assert (!mark->has_bias_pattern(), "invariant") ;

  if (mark->is_neutral()) {
    hash = mark->hash();              // this is a normal header
    if (hash) {                       // if it has hash, just return it
      return hash;
    }
    hash = get_next_hash(Self, obj);  // allocate a new hash code
    temp = mark->copy_set_hash(hash); // merge the hash code into header
    // use (machine word version) atomic operation to install the hash
    test = (markOop) Atomic::cmpxchg_ptr(temp, obj->mark_addr(), mark);
    if (test == mark) {
      return hash;
    }
    // If atomic operation failed, we must inflate the header
    // into heavy weight monitor. We could add more code here
    // for fast path, but it does not worth the complexity.
  } else if (mark->has_monitor()) {
    monitor = mark->monitor();
    temp = monitor->header();
    assert (temp->is_neutral(), "invariant") ;
    hash = temp->hash();
    if (hash) {
      return hash;
    }
    // Skip to the following code to reduce code size
  } else if (Self->is_lock_owned((address)mark->locker())) {
    temp = mark->displaced_mark_helper(); // this is a lightweight monitor owned
    assert (temp->is_neutral(), "invariant") ;
    hash = temp->hash();              // by current thread, check if the displaced
    if (hash) {                       // header contains hash code
      return hash;
    }
    // WARNING:
    //   The displaced header is strictly immutable.
    // It can NOT be changed in ANY cases. So we have
    // to inflate the header into heavyweight monitor
    // even the current thread owns the lock. The reason
    // is the BasicLock (stack slot) will be asynchronously
    // read by other threads during the inflate() function.
    // Any change to stack may not propagate to other threads
    // correctly.

    // 警告:
    //被替换的标题是完全不可变的。
    //在任何情况下都无法更改。所以我们有
    //将标题扩展到重量级监视器
    //甚至当前线程拥有锁。原因
    //是BasicLock(堆栈槽)是异步的
    //在inflate()函数中由其他线程读取。
    //对堆栈的任何更改都不会传播到其他线程
    //正确
  }

方法不想看,看了也看不懂,但是可以确定,hashcode并不是我们想象的返回的是内存地址那么简单,所以不同对象hashcode是可能相同的,所以不能通过比较hashcode来比较对象。

那么来梳理一下hashcode和equals方法的区别:

equals相同(同一对象),hashcode一定相同

equals不同(不同对象),hashcode可能相同

如何记忆好呢?看下图

AB是不同的对象,但是hashcode相同,A C hashcode不同,所以一定不是统一对象。总结起来就是,一个hashcode可能对应多个对象,一个对象只能有一个hashcode.

 

知道了hashcode和equals方法的区别和联系。那么hashcode到底存在哪呢?

答案是存在对象的对象头中,所以,对象头又是个什么东西?

首先,对象保存在内存中时,一共包含三部分

Java对象保存在内存中时,由以下三部分组成:

1,对象头

2,实例数据

3,对齐填充字节

hashcode就存在对象头

对象头存放的信息如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等。这部分数据长度在32位和63位的虚拟机中分别为32bit和64bit,官方称它为"Makr Word"。对象要存储的运行时数据很多,其实已超出32位、64位Bitmap结构所以记录的限度,但对象头信息是与对象自身定义的数据无关的额外存储成本,考虑到虚拟机的空间效率,Mark Word被设计成一个非固定的数据结构以便在极小的空间内存储尽量多的信息。它会根据对象状态复用自己的存储空间。
下面表示32位HotSpot虚拟机中,如果对象处理未被锁定状态,Mark Word的32位空间分成:25bit用于存储对象哈希码,4bit用于存储对象分代年龄,2bit用于存储锁标志位,1bit固定为0,而在其他状态(轻量级锁定、重量级锁定、GC标记、可偏向)下对象的存储内容如下:

参考:https://blog.csdn.net/smileiam/article/details/80364641

 

在一般情况下,hashcode不需要重写。什么情况下重写了equals方法需要重写hashcode呢?只有使用到容器时才需要,假设只重写了equals方法,那么两个equals比较相同的对象有可能因为hashcode不同定位到容器中不同的位置,这样容器中就有重复的元素了,而有些容器是不允许有重复的元素出现的。

那么如果没有重写equals,那么相同对象的equals相同,hashcode一定相同,不重写按道理来说是可以的。但是这就没有意义了,所以大部分还是要重写的。map中,我们平常使用的一般是String类型作为key,而String类型已经帮我们重写好了,所以不需要我们重写。

 

重写hashcode原则?

1.对象多次调用hashcode方法,必须返回一样的整数。

2.equals方法比较相同,hashcode也必须相同。

3.equals方法不同,hashcode可相同也可不同,但是为了减少冲突,应该尽量不同。

如何重写?

1.返回固定的同一个整数,该方法缺点,hashcode固定,所以同一个类的多个实例存放位置一样,导致冲突,性能低。

2.采用属性线性组合,具体点就是使用对象中的实例属性的hashcode和基本数据类型的值的线性组合

如:

String name;
int age;
int score;

public int hashcode(){
    return this.name.hashCode()+29*age+31*score;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值