java.util.Hashtable
提供的哈希表。
import java.util.*;
class BeanA {
private int i;
public BeanA(int i) {
}
public String toString() {
}
public boolean equals(Object o) {
}
public int hashCode() {
}
}
public class HashCodeTest {
public static void main(String[] args) {
}
}
我们在类BeanA中重写了equals和hashcode方法,这样在存储到HashSet数据集中,将保证不会出现重复的数据;如果把这两个方法去掉后,那些重复的数据仍会存入HashSet中,这就与HashSet强调的元素唯一性相违背,大家可以把这两个方法注释掉再运行一下。
因此,我们就可以理解在一些java类中什么情况下需要重写equals和hashcode。比如:在hibernate的实体类中,往往通过一个主键(或唯一标识符)来判断数据库的某一行,这就需要重写这两个方法。因为,Hibernate保证,仅在特定会话范围内,持久化标识(数据库的行)和Java标识是等价的。因此,一旦 我们混合了从不同会话中获取的实例,如果希望Set有明确的语义,就必 须实现equals() 和hashCode()。
如果你的对象想散列存储的集合中或者想作为散列Map的Key时(HashSet、HashMap、Hashtable等)那么你必须重写equals()方法,这样才能保证唯一性。在重写equals()方法的同时,必须重写hashCode()方法?当然,在这种情况下,你不想重写hashCode()方法,也没有错,但是sun建议这么做,重写hashCode只是技术要求(为了提高效率)。
当在散列集合中放入key时,将自动查看key对象的hashCode值,若此时放置的hashCode值和原来已有的hashCode值相等,则自动调用equals()方法,若此时返回的为true则表示该key为相同的key值,只会存在一份。
Object中关于hashCode和equals方法的定义为:
- public boolean equals(Object obj) {
- return (this == obj);
- }
- public native int hashCode();
基类的hashCode是一个native方法,访问操作系统底层,它得到的值是与这个对象在内存中的地址有关。
Object的不同子类对于equals和hashCode方法有其自身的实现方式,如Integer和String等。
equals相等的,hashCode必须相等
hashCode不等的,则 equals也必定不等。
hashCode相等的 equals不一定相等(但最好少出现 hashCode相等的情况)。
HashMap的put(K, Value)方法提供了一个根据K的hashCode来计算Hash码的方法hash()
- transient Entry[] table;
- public V put(K key, V value) {
- if (key == null)
- return putForNullKey(value); //HashMap支持null的key
- int hash = hash(key.hashCode()); //根据key的hashCode计算Hash值
- int i = indexFor(hash, table.length); //搜索指定Hash值在对应table中的索引
- for (Entry<K,V> e = table[i]; e != null; e = e.next) { //在i索引处Entry不为空,循环遍历e的下一个元素
- 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;
- }
- }
- //若i索引处Entry为null,表明此处还没有Entry
- modCount++;
- addEntry(hash, key, value, i); //将key、value添加到i索引处
- return null;
- }
- static int hash(int h) {
- h ^= (h >>> 20) ^ (h >>> 12);
- return h ^ (h >>> 7) ^ (h >>> 4);
- }
对于任意给定的对象,只有它的hashCode()返回值相同,那么程序调用hash(int h)方法所计算得到的Hash码值总是相同的。接下来会调用indexFor(int h, int length)方法来计算该对象应该保存在table数组的哪个索引处。
它总是通过h & (table.length - 1)来得到该对象的保存位置--而HashMap底层数组的长度总是2的n次方,这样就保证了得到的索引值总是位于table数组的索引之内。
当通过key-value放入HashMap时,程序就根据key的hashCode()来觉得Entry的存储位置:若两个Entry的key的hashCode()相同那么他们的存储位置相同;若两个Entry的key的equals()方法返回true则新添加Entry的value将覆盖原有Entry的value,但key不会覆盖;若两个Entry的key的equals()方法返回false则新加的Entry与集合中原有的Entry形成Entry链。
HashSet的add(E)的实现是通过HashMap的put方法来实现的。(HashSet内部是通过HashMap来实现的,TreeSet则是通过TreeMap来实现的)
根据key的hashCode计算器Hash值,然后取得该Hash值在表table的索引,取得该索引i对应的table中的Entry,判断key的equals()。