K:java中的hashCode和equals方法
hashCode和equals方法是Object类的相关方法,而所有的类都是直接或间接的继承于Object类而存在的,为此,所有的类中都存在着hashCode和equals。通过翻看Object类的相关源码,发现其hashCode的实现方式如下:
public native int hashCode();
从中可以看出,hashCode的实现是一个本地方法,并且其返回了一个int型的值。很多人都认为在默认情况下,hashCode返回的就是对象的存储地址,事实上这样的看法是不全面的,确实有些JVM在实现时是直接返回对象的存储地址的,但是在大多数的时候,其只能说是与存储地址有一定的关联,下面的是HotSpot JVM中生成hash散列值的实现:
@代码来源:http://www.cnblogs.com/dolphin0520/p/3681042.html
static inline intptr_t get_next_hash(Thread * Self, oop obj) {
intptr_t value = 0 ;
if (hashCode == 0) {
// This form uses an unguarded global Park-Miller RNG,
// so it's possible for two threads to race and generate the same RNG.
// On MP system we'll have lots of RW access to a global, so the
// mechanism induces lots of coherency traffic.
value = os::random() ;
} else
if (hashCode == 1) {
// This variation has the property of being stable (idempotent)
// between STW operations. This can be useful in some of the 1-0
// synchronization schemes.
intptr_t addrBits = intptr_t(obj) >> 3 ;
value = addrBits ^ (addrBits >> 5) ^ GVars.stwRandom ;
} else
if (hashCode == 2) {
value = 1 ; // for sensitivity testing
} else
if (hashCode == 3) {
value = ++GVars.hcSequence ;
} else
if (hashCode == 4) {
value = intptr_t(obj) ;
} else {
// Marsaglia's xor-shift scheme with thread-specific state
// This is probably the best overall implementation -- we'll
// likely make this the default in future releases.
unsigned t = Self->_hashStateX ;
t ^= (t << 11) ;
Self->_hashStateX = Self->_hashStateY ;
Self->_hashStateY = Self->_hashStateZ ;
Self->_hashStateZ = Self->_hashStateW ;
unsigned v = Self->_hashStateW ;
v = (v ^ (v >> 19)) ^ (t ^ (t >> 8)) ;
Self->_hashStateW = v ;
value = v ;
}
value &= markOopDesc::hash_mask;
if (value == 0) value = 0xBAD ;
assert (value != markOopDesc::no_hash, "invariant") ;
TEVENT (hashCode: GENERATE) ;
return value;
}
ps:该实现位于hotspot/src/share/vm/runtime/synchronizer.cpp文件下。
hashCode方法的主要作用是为了配合基于散列的集合一起正常运行,这样的散列集合包括HashSet、HashMap以及HashTable。Hash相关的数据结构是根据对象的相关信息(可以称为键),通过一定的运算规则将其散列映射到一个数值上的,为此,Hash相关的数据结构具有查找和插入的速度都较快的优点。Java中的hashCode方法就是根据一定的规则将与对象相关的信息(比如对象的存储地址,对象的字段等)映射成一个数值,这个数值称作为散列值也称为哈希值。而需要注意的一点是在一般情况下即使两个对象的hash值相同也不能判定这两个对象的相关信息是相同的,因为hash函数一般而言都不会是一个双射函数,为此有碰撞产生的情况存在(即两个不同的对象会得到相同的hash值)。为此,在比较两个对象是否相同的时候,便需要有equals方法作为辅助了。
至于equals方法,相信学过java基础的人都会知道,在String等类中其用于判断两个对象的值是否相等,至于在Object类中,其实现如下:
public boolean equals(Object obj) {
return (this == obj);
}
从中可以发现,对于Object类来说,其equals方法判断的只是两个对象是不是同一个对象而已,当为同一个对象的时候返回true,否则返回false。而这并不能够实现判断两个对象的值是否相同的功能,通过查看String类中的equals方法的相关源码:
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = count;
if (n == anotherString.count) {
char v1[] = value;
char v2[] = anotherString.value;
int i = offset;
int j = anotherString.offset;
while (n-- != 0) {
if (v1[i++] != v2[j++])
return false;
}
return true;
}
}
return false;
}
我们可以发现,其重写了Object类的equals方法,使得其可以对两个对象的值是否一致进行判断。
至此,我们对于hashCode和equals方法的作用有了一定的了解,hashCode方法的作用是根据对象的相关信息(对象的字段值,对象的存储地址)通过一定的运算规则获得一个int型值,该int值在一定程度上反应了对象的有关信息。而equals方法的作用是用于判断两个对象的相关的指定信息是否是一样的。在Hash相关的数据结构中通过结合使用hashCode方法以及equals方法来提高插入和查找效率。
例如,java.util.HashMap的中put方法的具体实现:
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;
}
一般而言,对于使用Hash相关的数据结构,其会先通过调用对象的hashCode方法获取该对象的hash值,缩小了查找范围之后,再通过调用equals方法进行“精细”的查找,以缩短查找时间和范围,提高查找效率。
通过以上的分析,我们需要注意到如下的几点:
- 不同的对象可能会生成相同的hash值。为此,不能通过hash值来判断两个对象是否相等,但是可以通过hash值来判断两个对象的不相等,即hash值相等的两个对象,其不一定是相等的,而hash值不相等的两个对象,其一定是不相等的。
- equals方法返回结果为ture的一定是相等的对象
由以上的这两点,我们可以推出如下的四点内容:
- 如果调用equals方法得到的结果为true,则两个对象的hashcode值必定相等
- 如果equals方法得到的结果为false,则两个对象的hashcode值不一定不同
- 如果两个对象的hashcode值不等,则equals方法得到的结果必定为false
- 如果两个对象的hashcode值相等,则equals方法得到的结果未知。
为此,我们可以知道,在重写equals方法的时候,必须要重写了hashCode方法。
在重写equals方法和hashCode方法的时候,需要注意以下几点:
- 在程序执行期间,只要equals方法的比较操作用到的信息没有被修改,那么对这同一个对象调用多次,hashCode方法必须始终如一地返回同一个整数。
- 如果两个对象根据equals方法比较是相等的,那么调用两个对象的hashCode方法必须返回相同的整数结果。
- 如果两个对象根据equals方法比较是不等的,则hashCode方法不一定得返回不同的整数。
------------------------------------------->摘自《Effective Java》
重写equals和hashCode方法的一个例子:
public class HashCodeTest
{
private String name="小小";
private int age=12;
@Override
public int hashCode()
{
return age*37+name.hashCode();
}
@Override
public boolean equals(Object other)
{
if(other==this)
return true;
if(other==null)
return false;
if(other instanceof HashCodeTest)
{
HashCodeTest t=(HashCodeTest) other;
return this.age==t.age&&this.name.equals(t.name);
}
return false;
}
}
注意的一点是:
在设计hashCode方法和equals方法的时候,如果对象中的数据易变,则最好在equals方法和hashCode方法中不要依赖于该字段