写在前面
java 当中的hashcode
参考
主要内容
在Java的Object类中有一个方法:
public native int hashCode();
这个方法是一个本地方法,也就是其他语言实现的方法。所以在Object的源码当中并不能看到这个方法的实现。hashCode方法的主要作用是为了配合基于散列的集合一起正常运行,这样的散列集合包括HashSet、HashMap以及HashTable。
为什么需要给所有的对象提供这样一个hashcode方法呢?
考虑一种情况,当向集合中插入对象时,如何判别在集合中是否已经存在该对象了?
一般是采用equals方法进行判断但是这个方法调用的开销是比较大的,如果采用hashcode的性质就比较好操作了,因为hashcode不同那么就肯定对象不同,所以可以先通过对hashcode进行哈希,得到位置(此项操作常数时间),然后再去判断该对象和所求位置上的对象的值是不是相同此时使用equals方法,因为hashcode相同的两个对象不一定就相同。所以所有的哈希数据结构都是散列的对象的hashcode。
说通俗一点:Java中的hashCode方法就是根据一定的规则将与对象相关的信息(比如对象的存储地址,对象的字段等)映射成一个数值,这个数值称作为散列值。hash值来源于这个对象的内部地址转换成的整型值。
public V put(K key, V value) {
if (key == null)
return putForNullKey(value);
int hash = hash(key.hashCode());
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}
put方法是用来向HashMap中添加新的元素,从put方法的具体实现可知,会先调用hashCode方法得到该元素的hashCode值,然后查看table中是否存在该hashCode值,如果存在则调用equals方法重新确定是否存在该元素,如果存在,则更新value值,否则将新的元素添加到HashMap中。从这里可以看出,hashCode方法的存在是为了减少equals方法的调用次数,从而提高程序效率。
hashCode返回的就是对象的存储地址,事实上这种看法是不全面的,确实有些JVM在实现时是直接返回对象的存储地址,但是大多时候并不是这样,只能说可能存储地址有一定关联。
因此有人会说,可以直接根据hashcode值判断两个对象是否相等吗?
肯定是不可以的,因为不同的对象可能会生成相同的hashcode值。虽然不能根据hashcode值判断两个对象是否相等,但是可以直接根据hashcode值判断两个对象不等,如果两个对象的hashcode值不等,则必定是两个不同的对象。如果要判断两个对象是否真正相等,必须通过equals方法。
- 对于两个对象,如果调用equals方法得到的结果为true,则两个对象的hashcode值必定相等;
- 如果equals方法得到的结果为false,则两个对象的hashcode值不一定不同;
- 如果两个对象的hashcode值不等,则equals方法得到的结果必定为false;
- 如果两个对象的hashcode值相等,则equals方法得到的结果未知。
euqals()方法
Object类定义的equals方法为:
public boolean equals(Object obj) {
return (this == obj);
}
也就是说继承自Object的所有类都有有equals这个方法进行对象之间的比较。因为java当中是没有操作符重载的所以简单的使用 == 操作符进行判断在java当中并不能方便的使用,需要使用equals进行对象相等的判断。
java当中的 == 操作符仅仅只能判断两个对象的值是否相等,如果是基本数据类型就判断值是否相等,如果是引用数据类型判断的是引用变量存储的值也就是地址是否相等。
String类重写了equals方法:
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
String类的equals方法判断两个String对象相等是先判断地址是否相等,再判断地址不同的时候这两个对象的值是否相等。
同时String也重写了hashcode 方法:
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
String类的值是不可修改的,所以其hashcode只跟其存储的值有关所以两个String对象如果值相等必然hashcode就相等。但是hashcode相等时其值是不是相等这是不确定的。所以判断两个String 对象是否值相等得一个一个比较才行不能简单的采用hashcode进行判断。
Object类的toString方法如下:
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
该方法就是输出了对象的类名加上其hashcode组成的字符串。
System.out对象的println(Object x)方法如下:
public void println(Object x) {
String s = String.valueOf(x);
synchronized (this) {
print(s);
newLine();
}
}
可见在输出x之前先获取x对象的String信息,输出的也是这个String信息,其实也就是调用对象的toString方法。
public static String valueOf(Object obj) {
return (obj == null) ? "null" : obj.toString();
}
所以对象的toString方法的重写还是有很大的作用的。