Object类中
equals()和hashCode()方法都是Object类中的方法(其他类都是从这个方法中继承而来的),不过hashCode()是native函数不开源(native函数为本地函数和平台相关,一般都是用C来实现的,已经与Java语言没什么关系)
equals()
public boolean equals(Object obj) {
return (this == obj);
}
一看就知道是判断两个对象的地址(即引用)是否相等。但是我们必需清楚,当String、Math、还有Integer、Double……等这些封装类在使用equals()方法时,已经覆盖了object类的equals()方法。
比如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对象中的内容进行比较
Java语言中对equals()的要求
类推性:如果x.equals(y)返回是“true”,而且y.equals(z)返回是“true”,那么z.equals(x)也应该返回是“true”。
还有一致性:如果x.equals(y)返回是“true”,只要x和y内容一直不变,不管你重复x.equals(y)多少次,返回都是“true”。
-
任何情况下,x.equals(null),永远返回是“false”;x.equals(和x不同类型的对象)永远返回是“false”。
(以上这五点是重写equals()方法时,必须遵守的准则)
hashCode()
public native int hashCode();
是与平台相关的native函数,返回值为:这个对象的哈兮地址
hashcode方法在把对象放到一个对象容器时大派用场,一个好的hashcode算法和坏的算法,在把对象放入容器和从容器取出时,效率相差极大。
看不到Object类的hashCode()实现,那我们看一下String类重写的hashCode()的实现:
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {//value数组里面放的是String对象的字符
char val[] = value;
//int算法 s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
// 就是用这个算法来计算String的hash地址的
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}// @return a hash code value for this object.
equals()和hashCode()的关系
equals()相等的对象,hashCode()一定相等:(物理地址都确定相等,肯定是一个对象了)
hashCode()相等的对象,equals()相等或不等:(你还记得散列表么,采用直接寻址技术,在理想状态下无须任何比较就可以找到待查关键字,查找的期望时间为o(1)。有的时候hash地址相同,但发现这个地址有元素后就会去比较和这个地址中的元素是否equals,不equals的话就要再散列找个地方存放对象。hash地址一般并不是对象存储地址,而是一个可以映射到对象物理地址的地址)
equals()不相等的对象,hashCode()有可能相等:(原因的话就是和上面一样,由哈兮冲突导致)
hashCode()不相等的对象,equals()一定不相等:(我们可以这要理解问题:哈兮码是根据关键码值(或者说对象的某些特征)来计算的来的。你想哦,比较两个对象的哈兮码,如果相同(我们可以理解为这两个对象的某些特征是相同的,但你能保证其他特征也相同吗?),如果不同(我们可以理解我,这两个对象连某些特定的特征都不相同,那这两个对象肯定不相同了))
一个实例
public static void main(String[] args) {
// TODO Auto-generated method stub
String s1=new String("zhangjinyu");
String s2=new String("zhangjinyu");
System.out.println("s1==s2:"+(s1==s2));
System.out.println("s1.equals(s2):"+s1.equals(s2));
System.out.println("s1.hashCode()==s2.hashCode():"+(s1.hashCode()==s2.hashCode()));
System.out.println("s1.hashCode():"+s1.hashCode());// String类重写了hashCode()
System.out.println("s2.hashCode():"+s2.hashCode());
Set hash_set=new HashSet();
hash_set.add(s1);
hash_set.add(s2);
System.out.println(hash_set);
System.out.println("hash_set.hashCode():"+hash_set.hashCode());// hash_set运行的hashCode()是AbstractSet中实现的
}
运行结果:
(一)hash_set中只有一个对象
s1==s2:false
s1.equals(s2):true
s1.hashCode()==s2.hashCode():true
s1.hashCode():1943454207
s2.hashCode():1943454207
[zhangjinyu]
hash_set.hashCode():1943454207
(二)hash_set中有两个不同的对象:s2 zhangjinyu → zhangjin
s1==s2:false
s1.equals(s2):false
s1.hashCode()==s2.hashCode():false
s1.hashCode():1943454207
s2.hashCode():-1432612957
[zhangjinyu, zhangjin]
hash_set.hashCode():510841250
来浅析一下为什么hash_set.hashCode()==s1.hashCode()==s2.hashCode()?
答案: 我改了下s2,发现就不会相等了,原来是这样:HashMap中的hashCode的hash算法与他的每个元素有关,上面之所以相同是因为hash_set中只有一个元素
// HashSet.class
private transient HashMap<E,Object> map;
private static final Object PRESENT = new Object();// 额,是个常量,并且是final型的,这是jdk为了方便将value设为常量PRESET,因为value是可以重复的。
public boolean add(E e) {
return map.put(e, PRESENT)==null;// 所以说,调用一下HashSet的add方法,实际上就是想HashMap中增加一行(key-value)记录;
}
// HashMap.class
public V put(K key, V value) {
if (key == null)
return putForNullKey(value);
// 计算key对象的hash码
// 用key的hash码的int值和table的长度(也就是key-value对数)进行“与”计算
// 结果为这个可以对象在Entry[] table数组中的下标
int hash = hash(key);
int i = indexFor(hash, table.length);
// 这个table有讲究,他是transient Entry<K,V>[] table;
// 而Entry是实际上真正存放key-value对的地方
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;
}
// 计算HashMap中的Key值的hash码
// hash方法就是我们在数据结构中讲的散列函数。它是经过放进HashSet里面的对象作为key得到hashCode码,在进行散列得到的一个整数。
final int hash(Object k) {
int h = 0;
if (useAltHashing) {
if (k instanceof String) {
return sun.misc.Hashing.stringHash32((String) k);
}
h = hashSeed;
}
h ^= k.hashCode();// k.hashCode()用k对象的hashCode()得到hash码
// 这是一个散列算法
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
**** hash_set.hashCode() ****
// 这里,说明hash_set.hashCode()的值为hash_set中每个对象的hashCode的和
public int hashCode() {
int h = 0;
Iterator<E> i = iterator();
while (i.hasNext()) {
E obj = i.next();
if (obj != null)
h += obj.hashCode();
}
return h;
}
}
参考文献:
ITeye作者“zhaoxudonglove”的文章《java中hashcode()和equals()的详解》
作者“L.G.Alexander”在ITeye中的文章《HashSet与HashMap关系之源码分析》,分析的相当认真,感谢这位作者的用心。