equals和”==”
equals()是Object
方法,用于比较两个对象是否相等,相同功能的还有”==”,”==”和equals的区别:
- equals不能作用于基本数据类型,作用于引用类型时,如果不重写的话和”==”效果一样是比较对象地址(诸如
String
、Date
等类对equals方法进行了重写的话,比较的是所指向的对象的内容) - “==”作用于基本数据类型比较的是字面值,作用于引用类型时,比较对象地址
在比较字符串时候,要考虑到常量池
String s1 = "abc";
String s2 = "abc";
System.out.println(s1.equals(s2)); // true
System.out.println(s1 == s2); // true,常量池
String str1 = new String(“abcd”);
String str2 = new String(“abcd”);
System.out.println(str1.equals(str2)); // true,重写了equals方法
System.out.println(str1 == str2); // false 这里创建了两次对象,一次是在常量池中创建了对象"abc",一次是在堆内存中创建了对象str1,所以str1和str2的地址值不相等。
hashCode
hashCode在散列集合包括HashSet、HashMap以及HashTable里,对每一个存储的桶元素都有一个唯一的”块编号”,即它在集合里面的存储地址。
默认计算规则【1】
调用hashCode方法默认返回的值被称为identityHashCode(标识哈希码)。
在hashCode方法注释中,说通常hashCode一般是通过对象内存地址映射过来的。
/**
* As much as is reasonably practical, the hashCode method defined by
* class {@code Object} does return distinct integers for distinct
* objects. (This is typically implemented by converting the internal
* address of the object into an integer, but this implementation
* technique is not required by the
* Java™ programming language.)
*/
public native int hashCode();
OpenJDK的源码提供了6种生成hash值的方法:
0. A randomly generated number.
1. A function of memory address of the object.
2. A hardcoded 1 (used for sensitivity testing.)
3. A sequence.
4. The memory address of the object, cast to int.
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源码一致。
685 mark = monitor->header();
...
687 hash = mark->hash();
688 if (hash == 0) {
689 hash = get_next_hash(Self, obj);
...
701 }
...
703 return hash;
get_next_hash()
,这个函数提供了6种生成hash值的方法:
根据globals.hpp,OpenJDK8默认采用第五种方法。而 OpenJDK7 和 OpenJDK6 都是使用第一种方法,即 随机数生成器。
equals和hashCode
规则:
- equals相等,hashCode一定相等
- 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