背景知识
1.Java对象的hashCode与内存地址的关系
参考这篇文章。 可以得出如下结论:
- hashcode不一定能代表内存地址不相同。不同的JVM有不同的实现。openjdk 源码里生成 hashCode算法有六种,可以通过
-XX:hashCode=N
指定,N取值0到5。n=1
时算法取值为对象的内存地址,默认是n=5
算法。
/** 启动该单元测试时,指定JVM参数-XX:hashCode=2 这个参数让所有对象的hashCode为2*/
public class HashCodeTest {
@Test
public void test() {
Demo d1 = new Demo("1");
System.out.println("#### hashCode d1 : " + d1.hashCode());
Demo d2 = new Demo("1");
System.out.println("#### hashCode d2 : " + d2.hashCode());
}
}
class Demo {
String key;
Demo(String key) {
this.key = key;
}
}
输出如下
#### hashCode d1 : 2
#### hashCode d2 : 2
2.object1 == object2
比较的是hashcode
还是内存地址
重写hashCode,让每个对象返回的hashCode值都一样
public class HashCodeTest {
@Test
public void test() {
Demo d1 = new Demo("1");
System.out.println("#### hashCode d1 : " + d1.hashCode());
Demo d2 = new Demo("1");
System.out.println("#### hashCode d2 : " + d2.hashCode());
System.out.println("#### hashCode is equal : " + (d1.hashCode() == d2.hashCode()));
System.out.println("#### memory address is equal : " + (d1 == d2));
}
}
class Demo {
String key;
Demo(String key) {
this.key = key;
}
@Override
public boolean equals(Object o) {
Demo d = (Demo) o;
return this.key.equals(d.key);
}
@Override
public int hashCode() {
return 111;
}
}
输出如下
#### hashCode d1 : 111
#### hashCode d2 : 111
#### hashCode is equal : true
#### memory address is equal : false
通过如上代码可以得出结论:
object1 == object2
比较的是内存地址,非hashCode
3.HashMap中entry.hash == hash && (k == key || key.equals(k))
理解
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
entry.hash == hash && (k == key || key.equals(k))
在HashMap
的get
和put
方法都会用到。
在HashMap
中寻找key
对应的映射关系时,如果上述判断条件成立,则代表map中存在对应映射关系。
1.entry.hash == hash
在HashMap
中,会对key
的hashCode
值进行二次hash计算((h = key.hashCode()) ^ (h >>> 16)
),接下来在计算该key在数组中索引位置时,通过hash & (length - 1)
进行的计算。因此,不同的hash值计算得到的索引位置可能相同。
2.(k == key || key.equals(k))
先是比较内存地址是否相等,如果内存地址不相等,再去使用equals
方法判断两个对象是否相等。
2.1k == key
k == key
这里比较的是内存地址,防止hashmap
重复放入同一个对象(同一个对象的内存地址是相同的,如果k == key
成立说明放入的是同一个对象,没必要再去执行equals
方法)
2.2key.equals(k)
先说明hashCode 和 equals的关系:
1.两个对象equals的时候,hashCode必须相等。换句话说,两个对象hashCode不相等,那么equals一定不相等
2.hashCode相等,对象不一定equals。
根据1和2的描述,在HashMap
中,entry.hash == hash
似乎是多余的。假设去掉这个判断,如果两个key的hashCode
不相等,那么就会去执行equals
方法,根据1的描述,hashCode
不相等,equals方法也不会相等。有无entry.hash == hash
语句,判断结果都是相同的,那么这里为什么要加上这个判断呢?
因为如果没有entry.hash == hash
这个判断,所有的都会执行equals
方法,效率比较低。如果hashCode
不相等,那么equals
方法也不会相等,没必要再去执行equals
方法。