zoukankan      html  css  js  c++  java
  • 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值的低位更随机均匀。

  • 相关阅读:
    题解 [BZOJ1295][SCOI2009] 最长距离
    题解 [51nod1274] 最长递增路径
    #leetcode刷题之路21-合并两个有序链表
    #leetcode刷题之路20-有效的括号
    #leetcode刷题之路19-删除链表的倒数第N个节点
    #leetcode刷题之路18-四数之和
    #leetcode刷题之路17-电话号码的字母组合
    #leetcode刷题之路16-最接近的三数之和
    #leetcode刷题之路15-三数之和
    #leetcode刷题之路14-最长公共前缀
  • 原文地址:https://www.cnblogs.com/jcli/p/2132191.html
Copyright © 2011-2022 走看看