HashSet中对象的equal和hashCode方法重要性

大家都知道,平时我们要用到的集合大部分是List和Set。集合,在数学严格定义上讲,具有以下三个特征:

  1. 确定性:集合中的元素应该是确定的,不能模棱两可。
  2. 互异性:集合中的元素应该是互不相同的,相同的元素在集合中只能算作一个。
  3. 无序性:集合中的元素是无次序关系的。 

根据上面三个特征,所以我觉得Set才是真正意义上的集合概念。而List(可以有重复的元素)最多只能算是一个容器,把具有一致性的东西都可以装在里面。

至于“确定性”和“无序性”,这里不讨论。那都是数学上的抽象概念。我们这里只讨论集合中元素的唯一性问题。

其 实集合的存在,就是对集合一系列的操作,比如数学上我们有相交,相并,补集的概念。而具体到程序里集合的操作,包括增加一个元素,删除一个元素,等等。前 面我们说到过,元素在集合里必需是唯一的,而唯一的标准是什么?我们说两个实体是同一的,一般都会先列出某些个标准,然后对应着一一验证 ,如果都能成立,则同一性成立。比如说在学校里,用学号做为标准来标识学生的唯一性,在商场里,用条形码(其实也是数字编码)标识商品的唯一性。而在程序 里,我们怎么来定义标准呢?

你说的对,就是对象的equal方法,我们在进行集合的操作时,就是用equal方法来判断对象是否唯一。
这里要强调的是,在java里千万要警惕 == 操作符,==是判断两个对象的物理地址的,没有任何业务意义。

class Student{

    private String code;

    private String name;

    public Student(String code,String name){

       this.code = code;

       this.name = name;

    }

    public boolean equals(Object obj){

       if(obj == null)

           return false;

       if(!(obj instanceof Student))

           return false;

       Student p = (Student)obj;

       return code.equals(p.code);

    }

}

代码中重写了equals方法,如果两个Student实例中code相等,则认为是相同的对象。
写个测试代码:

public static void main(String[] args) throws Exception{

       Set<Student> ss = new HashSet<Student>();

       Student s = new Student("001","lily");

       Student s1 = new Student("001","lily");

       ss.add(s);

       ss.add(s1);

       System.out.println(ss.size());

    }

输出:2

你可能会问,为什么重写了equals方法,s和s1实例的code都为"001",为什么集合中仍可以放两个对象呢?

原因就在于HashSet的实现是通过HashMap的KeySet做为存储结构的,大家都知道哈希算法为了避免键值冲突,一般有两个方法:二次哈希和链地址法。则HashMap就是用的链地址法。

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;

    }

看上面代码中下划线的黑体字,就是用hashCode()方法得到键值的桶(桶的概念是一个具有相同哈希值的容器)地址,然后才在一个桶中调用equals方法比较对象是否相等。

其实到这里我们已经知道了hashCode为什么一定要重写了。如果为两个实例即使equals方法成立,可是如果不在一个桶中,则都本不会调用equals方法比较了。

 所以,上面的Student类为了保证唯一性,必需重写hashCode方法:

public int hashCode(){

       return Integer.valueOf(code);

 }

这里要提下HashMap源码中要注意的两个方法:

static int hash(int h) {

     h ^= (h >>> 20) ^ (h >>> 12);

     return h ^ (h >>> 7) ^ (h >>> 4);

 }

 static int indexFor(int h, int length) {

     return h & (length-1);

  }

indexFor方法是根据算出来的哈希值和哈希表长度进行and操作,所以为了保证哈希表分布的均匀性,所以要用hash(key.hashCode())再次进行哈希运算,为了保证最终计算出来的hash值的低位更随机均匀。

posted @ 2011-08-09 13:53  海鸟  阅读(1406)  评论(0编辑  收藏  举报