黑马软件工程师 java 分析 equals方法和hashCode方法
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(x)也应该返回是“true”。
-
反射性:x.equals(x)必须返回是“true”。
-
类推性:如果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关系之源码分析》,分析的相当认真,感谢这位作者的用心。