equals和”==”

equals()是Object方法,用于比较两个对象是否相等,相同功能的还有”==”,”==”和equals的区别:

  1. equals不能作用于基本数据类型,作用于引用类型时,如果不重写的话和”==”效果一样是比较对象地址(诸如StringDate等类对equals方法进行了重写的话,比较的是所指向的对象的内容)
  2. “==”作用于基本数据类型比较的是字面值,作用于引用类型时,比较对象地址

在比较字符串时候,要考虑到常量池

  1. String s1 = "abc";
  2. String s2 = "abc";
  3. System.out.println(s1.equals(s2)); // true
  4. System.out.println(s1 == s2); // true,常量池
  5. String str1 = new String(“abcd”);
  6. String str2 = new String(“abcd”);
  7. System.out.println(str1.equals(str2)); // true,重写了equals方法
  8. System.out.println(str1 == str2); // false 这里创建了两次对象,一次是在常量池中创建了对象"abc",一次是在堆内存中创建了对象str1,所以str1和str2的地址值不相等。

hashCode

hashCode在散列集合包括HashSet、HashMap以及HashTable里,对每一个存储的桶元素都有一个唯一的”块编号”,即它在集合里面的存储地址。

默认计算规则【1】

调用hashCode方法默认返回的值被称为identityHashCode(标识哈希码)。
在hashCode方法注释中,说通常hashCode一般是通过对象内存地址映射过来的。

  1. /**
  2. * As much as is reasonably practical, the hashCode method defined by
  3. * class {@code Object} does return distinct integers for distinct
  4. * objects. (This is typically implemented by converting the internal
  5. * address of the object into an integer, but this implementation
  6. * technique is not required by the
  7. * Java™ programming language.)
  8. */
  9. public native int hashCode();

OpenJDK的源码提供了6种生成hash值的方法:

  1. 0. A randomly generated number.
  2. 1. A function of memory address of the object.
  3. 2. A hardcoded 1 (used for sensitivity testing.)
  4. 3. A sequence.
  5. 4. The memory address of the object, cast to int.
  6. 5. Thread state combined with xorshift (https://en.wikipedia.org/wiki/Xorshift)

OpenJDK8默认采用第五种方法,基于线程状态的数字。而 OpenJDK7 和 OpenJDK6 都是使用第一种方法,即 随机数生成器。
详情分析参阅:JVM-高级篇-HotSpot VM 锁实现之hashCode

但是了解jvm的同学肯定知道,不管是标记复制算法还是标记整理算法,都会改变对象的内存地址。鉴于jvm重定位对象地址,但该hashCode又不能变化,那么该值一定是被保存在对象的某个地方了(在对象头的markword中)。
我们推测,很有可能是在第一次调用hashCode方法时获取当前内存地址,并将其保存在对象的某个地方,当下次调用时,只用从对象的某个地方获取值即可。但这样实际是有问题的,你想想,如果对象被归集到别的内存上了,那在对象以前的内存上创建的新对象其hashCode方法返回的值岂不是和旧对象的一样了?这倒没关系,java规范允许这样做(hashCode相同)。
以上都是我们的猜测,并没有实锤。看源码,hashCode方法是一个本地方法。

真正hashCode方法

hashCode方法的实现依赖于jvm(由C实现),不同的jvm有不同的实现,我们目前能看到jvm源码就是OpenJDK的源码,OpenJDK的源码大部分和Oracle的JVM源码一致。

  1. 685 mark = monitor->header();
  2. ...
  3. 687 hash = mark->hash();
  4. 688 if (hash == 0) {
  5. 689 hash = get_next_hash(Self, obj);
  6. ...
  7. 701 }
  8. ...
  9. 703 return hash;

get_next_hash(),这个函数提供了6种生成hash值的方法:
根据globals.hpp,OpenJDK8默认采用第五种方法。而 OpenJDK7OpenJDK6 都是使用第一种方法,即 随机数生成器

equals和hashCode

规则:

  1. equals相等,hashCode一定相等
  2. hashCode不相等,equals一定不相等

如果覆写了equals方法,必须覆写hashcode方法,原因是默认的hashCode是将对象的存储地址进行映射。而且逻辑上,如果两个对象的equals方法返回是相等的,那么它们的hashcode必须相等;反之不一定成立。

注意:即使重写hashCode和equals让两个对象相等,但底层还是两个对象,所以Synchronized这俩个对象锁不是同一把。但比如HashMap这些存储的时候会帮你做处理,因为HashMap比较的就是hashCode和equals。

参考

【1】:https://cloud.tencent.com/developer/article/1622192
【2】:https://www.zhihu.com/question/52116998