用 hashcode 的原因只有一个:效率。理论的说法它的复杂度只有 O(1)。试想我们把元素放在线性表里面,每次要找一个元素必须从头一个一个的找它的复杂度有 O(n)。如果放在平衡二叉树,复杂度也有 O(log n)。
为啥很多地方说“重写 equals 的时候一定要重写 hashCode”。说到这里我知道很多人知道有个原则:如果a.equals(b) 那么要确保 a.hashCode()==b.hashCode()。
如果放在 HashMap 和 Hashtable 里面是作为 value 而不是作为 key 的话也是不必 override hashCode了。至于HashSet,实际上它只是忽略 value 的 HashMap,每次 HashSet.add(o) 其实就是HashMap.put(o, dummyObject)。
那为什么放到 Hash 容器里面要 overide hashCode 呢?因为每次 get 的时候 HashMap 既要看 equals是不是true也要看 hash code 是不是一致,put 的时候也是要看 equals 和 hashcode。
如果说到这里您还是不太明白,咱就举个例子:
譬如把一个自己定义的 class Foo{ ... } 作为 key 放到 HashMap。实际上 HashMap 也是把数据存在一个数组里面,所以再 put 函数里面。HashMap 会调 Foo.hashCode() 算出作为这个元素在数组里面的下标,然后把 key 和 value 封装成一个对象放到数组。等一下,万一个对象算出来的 hashcode 一样怎么办?会不会替换掉?先回答第2个问题,会不会替换掉就要看 Foo.equals() 了,如果 equals() 也是 true 那就要替换掉了。万一是 false,就是所谓的 collision 了。当2个元素 hashCode 一样但是 equals 为 false 的时候,那个HashMap 里面的数组的这个元素就变成了链表。也就是 hashcode 一样的元素在一个链表里面,链表的头在那个数组里面。
回过来说get的时候,HashMap 也先调 key.hashCode() 算出数组下标,然后看 equals 如果是 true 就是找到了,所以就涉及了 equals。
假设如果有个 key 为 a 的元素在 HashMap 里面的情况:
1:如果这时候用 equals 为 true 但是 hashCode 不等的 b 作为 get 参数的话,这个时候 b 算出来的数组下标一定不是 a 所在的下标位置。
2:如果这时候用 equals 为 false 但是 hashCode 相等的 b 作为 get 参数的话,这个时候 b 算出来的数组下标是对了,但是用 equals 来寻找相符的 key 就找不到 a 了。
以上2种情况要么就是 get 找不到符合的元素返回 null,要么就是返回一个 hashCode 和 equals 恰好都符合 b 的另外的元素,这就产生了混乱。混乱的根本就是错误实现 hashCode 和 equals。
下面有个非常简化版的 HashMap 实现帮助大家理解,您甚至可以把它当作伪代码来看。(这个实现只是为了模拟 HashMap 的机制,所以参数检查,访问修饰符都忽略。在 Java.util.HashMap 里面的对 hash 值的二次 hash,根据数组长度计算 index,以及超出数组时的 resize 都忽略)
- /*
- * Just to demonstrate hash map mechanism,
- * Please do not use it in your commercial product.
- *
- * Argument checking and modifier are ignored.
- * In java.util.HashMap, array are used instead of ArrayList, so index of array is calculated by 'h & (length-1)',
- * while we use ArrayList to skip the calculation for simple.
- *
- * @author Shengyuan Lu 卢声远 <michaellufhl@yahoo.com.cn>
- */
- public class SimpleHashMap {
- ArrayList<LinkedList<Entry>> entries = new ArrayList<LinkedList<Entry>>();
- /**
- * Each key-value is encapsulated by Entry.
- */
- static class Entry {
- Object key;
- Object value;
- public Entry(Object key, Object value) {
- this.key = key;
- this.value = value;
- }
- }
- void put(Object key, Object value) {
- LinkedList<Entry> e = entries.get(key.hashCode());
- if (e != null) {
- for (Entry entry : e) {
- if (entry.key.equals(key)) {
- entry.value = value;// Match in lined list
- return;
- }
- }
- e.addFirst(new Entry(key, value));// Add the entry to the list
- } else {
- // Put the new entry in array
- LinkedList<Entry> newEntry = new LinkedList<Entry>();
- newEntry.add(new Entry(key, value));
- entries.add(key.hashCode(), newEntry);
- }
- }
- Object get(Object key) {
- LinkedList<Entry> e = entries.get(key.hashCode());
- if (e != null) {
- for (Entry entry : e) {
- if (entry.key.equals(key)) {
- return entry.value;
- }
- }
- }
- return null;
- }
- /**
- * Do we need to override equals() and hashCode() for SimpleHashMap itself?
- * I don't know either:)
- */
- }