一、hashCode基本介绍
hashCode方法是Object类中方法,Java中的每一个对象都有该方法,该方法返回的是一个整数值。该方法是一个本地方法。hashCode返回的默认值是该对象在JVM中的地址值的十进制形式。
public native int hashCode();
在jdk源码中关于hashCode方法的一些约定都写在了方法注释上。翻译结果如下图:
总结为以下3点:
- 如果一个对象的
equals
方法没有被重写,在对该对象多次调用hashCode
方法时,必须一致地返回相同的整数。 - 如果两个对象用
equals
比较相等,那么这两个对的hashCode
也必须相同。 - 如果两个对象用
equals
比较不相等,不要求hashCode
方法必须相同。
二、jdk中涉及到hashCode的源码
2.1 Object类中的toString方法
toString
方法返回的是对象的class全名+“@”+hashCode的十六进制。
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
2.2 HashMap类的put(K,V)方法
-
HashMap的数据结构是一个散列,关于HashMap的介绍可以参考这篇文章。
-
下面是put方法的源码:HashMap方法是不允许key重复的,在向HashMap中put元素时,源码中调用了
putVal
方法,但是调用该方法时,并没有直接将key传给该方法,而是将hash(key)
传给了putVal方法。接下来的判断key是否重复、以及为key寻址的过程指的都是hash(key)
,而不是一开始传入的key值。
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
- 上面提到的
hash(key)
方法源码如下:根据hash函数的源码可以看到,调用HashMap方法put方法时,真正使用的key都不是原始的key值,而是调用了key值的hashCode
方法进行计算得到的新值。
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
2.2 HashMap类的get(K)方法
- get(K)方法在根据key获取元素时,实际使用的key也是
hash(key)
方法返回的值。源码如下:
public V get(Object key) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
三、不按照jdk的约定的后果
3.1 不按照约定对toString方法的影响
- 该方法虽然调用了hashCode方法,但是hashCode是否重写对该方法影响不大,因为大多数的需要用到toString方法的封装类都对
toString
方法进行了重写。例如String、Integer等。
3.2 不按照约定对HashMap的put和get的影响
-
通过前面分析HashMap的put方法和get方法,可以知道:
put方法在添加元素之前
和get方法在获取元素之前
是用的都不是实际传入的key,而是hash(key)
方法的返回值,而hash(key)
方法中调用了key的hashCode
方法。 -
根据HashMap的底层实现原理:put方法在添加元素相当于
寻址(为元素寻找合适的位置)
和get方法在获取元素相当于根据元素的地址获取元素的值
,那么如果你在put(K,V)元素时和get(K)元素时K的hashCode()方法返回值不一致,就会导致get(K)获取元素时,找不到元素真正的位置,导致返回值是null。
举例如下:重写一个Demo类的hashCode方法。
public class Demo {
@Override
public int hashCode() {
return (int)(Math.random()*1000);//让每一次调用hashCode方法的返回值都是一个随机数。
}
public static void main(String[] args) {
Map map = new HashMap();
Demo d = new Demo();
map.put(d,1);
System.out.println(map.get(d));//结果是null,如果不重写可以获取到value值也就是1.
}
}