• 技术文章 >Java >java教程

    Java中hashcode()方法的前生与今世

    高洛峰高洛峰2016-11-22 15:45:37原创596
    当我们要学习hashcode()方法时,我们需要一步一步弄清楚以下这几个方面:

    1.hashcode()的由来

    hashcode是jdk根据对象的地址或者字符串或者数字算出来的int类型的数值,那么是通过什么算出来的呢?答案是hashtable(哈希表),哈希表就是根据关键码值(Key value)而直接进行访问的数据结构。这样的话可能太过于官方,在这里说一下我的理解,hashcode就是通过一个函数映射得到的值,这个函数叫哈希函数,我们向函数中传入一个key值,就会得到一个value值,通过哈希函数得到的value就是哈希值,即value=f(key)。那么理解了hashcode之后,我们就知道hashcode()方法是干什么用的了,hashcode()方法就是为了计算并返回hashcode方法。

    2.hashcode()的作用

    哈希表的主要好处就是它能够提高查找效率。通过查看API我们可以发现,hashcode()是Object的方法,也可以说正常情况下所有类的对象都够调用hashcode()方法,在Java中,根据他能够提高查找效率的特点,我们主要应用在为了配合基于散列的集合一起正常运行,这样的散列集合包括HashSet、HashMap以及HashTable。集合中不允许重复的元素存在,那么当我们需要加入一个新的元素时,如何去判断该元素已经存在了呢?也许大多数人都会想到调用equals方法来逐个进行比较,这个方法确实可行。但是如果集合中已经存在一万条数据或者更多的数据,如果采用equals方法去逐一比较,效率必然是一个问题。此时hashCode方法的作用就体现出来了,当集合要添加新的对象时,先调用这个对象的hashCode方法,得到对应的hashcode值,实际上在HashMap的具体实现中会用一个table保存已经存进去的对象的hashcode值,如果table中没有该hashcode值,它就可以直接存进去,不用再进行任何比较了;如果存在该hashcode值, 就调用它的equals方法与新元素进行比较,相同的话就不存了,不相同就散列其它的地址,所以这里存在一个冲突解决的问题,这样一来实际调用equals方法的次数就大大降低了。下面这段代码是java.util.HashMap的中put方法的具体实现:

    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;
        }

    put方法是用来向HashMap中添加新的元素,从put方法的具体实现可知,会先调用hashCode方法得到该元素的hashCode值,然后查看table中是否存在该hashCode值,如果存在则调用equals方法重新确定是否存在该元素,如果存在,则更新value值,否则将新的元素添加到HashMap中。从这里可以看出,hashCode方法的存在是为了减少equals方法的调用次数,从而提高程序效率。

    3.hashcode()方法与equals()方法

    在这里需要注意几个问题。判断对象相等是否可以用hashcode()方法,答案是不可以必须用equals()方法。两个不同对象可能hashcode相等,但两个不同hashcode的对象一定不同。

    另一个注意的问题:设计一个类的时候为需要重写equals方法,比如String类,但是千万要注意,在重写equals方法的同时,必须重写hashCode方法。下面看一个只重写了equals方法的例子:

     import java.util.HashMap;
    import java.util.HashSet;
    import java.util.Set;
     
    class People{
        private String name;
        private int age;
         
        public People(String name,int age) {
            this.name = name;
            this.age = age;
        }  
         
        public void setAge(int age){
            this.age = age;
        }
             
        @Override
        public boolean equals(Object obj) {
            // TODO Auto-generated method stub
            return this.name.equals(((People)obj).name) && this.age== ((People)obj).age;
        }
    }
     
    public class Main {
     
        public static void main(String[] args) {
             
            People p1 = new People("Jack", 12);
            System.out.println(p1.hashCode());
                 
            HashMap<People, Integer> hashMap = new HashMap<People, Integer>();
            hashMap.put(p1, 1);
             
            System.out.println(hashMap.get(new People("Jack", 12)));
        }
    }

    如果两个People对象,如果它的姓名和年龄相等,则认为是同一个人。这段代码本来的意愿是想这段代码输出结果为“1”,但是事实上它输出的是“null”。

    虽然通过重写equals方法使得逻辑上姓名和年龄相同的两个对象被判定为相等的对象(跟String类类似),但是要知道默认情况下,hashCode方法是将对象的存储地址进行映射。那么上述代码的输出结果为“null”就不足为奇了。原因很简单,p1指向的对象和System.out.println(hashMap.get(new People("Jack", 12)));这句中的new People("Jack", 12)生成的是两个对象,它们的存储地址肯定不同。所以在hashmap进行get操作时,因为得到的hashcdoe值不同直接返回null。如果想上述代码输出结果为“1”,很简单,只需要重写hashCode方法,让equals方法和hashCode方法始终在逻辑上保持一致性。

    import java.util.HashMap;
    import java.util.HashSet;
    import java.util.Set;
     
     
    class People{
        private String name;
        private int age;
         
        public People(String name,int age) {
            this.name = name;
            this.age = age;
        }  
         
        public void setAge(int age){
            this.age = age;
        }
         
        @Override
        public int hashCode() {
            // TODO Auto-generated method stub
            return name.hashCode()*37+age;
        }
         
        @Override
        public boolean equals(Object obj) {
            // TODO Auto-generated method stub
            return this.name.equals(((People)obj).name) && this.age== ((People)obj).age;
        }
    }
     
    public class Main {
     
        public static void main(String[] args) {
             
            People p1 = new People("Jack", 12);
            System.out.println(p1.hashCode());
                 
            HashMap<People, Integer> hashMap = new HashMap<People, Integer>();
            hashMap.put(p1, 1);
             
            System.out.println(hashMap.get(new People("Jack", 12)));
        }
    }

    下面引用一下Effective Java一书:

    在程序执行期间,只要equals方法的比较操作用到的信息没有被修改,那么对这同一个对象调用多次,hashCode方法必须始终如一地返回同一个整数。

    如果两个对象根据equals方法比较是相等的,那么调用两个对象的hashCode方法必须返回相同的整数结果。

    如果两个对象根据equals方法比较是不等的,则hashCode方法不一定得返回不同的整数。

      对于第二条和第三条很好理解,但是第一条,很多时候就会忽略。在《Java编程思想》一书中的P495页也有同第一条类似的一段话:

      “设计hashCode()时最重要的因素就是:无论何时,对同一个对象调用hashCode()都应该产生同样的值。如果在讲一个对象用put()添加进HashMap时产生一个hashCdoe值,而用get()取出时却产生了另一个hashCode值,那么就无法获取该对象了。所以如果你的hashCode方法依赖于对象中易变的数据,用户就要当心了,因为此数据发生变化时,hashCode()方法就会生成一个不同的散列码”。

    声明:本文原创发布php中文网,转载请注明出处,感谢您的尊重!如有疑问,请联系admin@php.cn处理
    专题推荐:Java
    上一篇:Spring 注解事件Event 下一篇:Spring框架知识总结-注入Bean的各类异常
    VIP会员

    相关文章推荐

    • 理解java8中java.util.function.*pojo反射新方法(附代码)• 浅析安卓app和微信授权登录及分享完整对接(代码分享)• 教你一招搞定时序数据库在Spring Boot中的使用• Android和IOS的浏览器中检测是否安装某个客户端的方法• 基于Android 监听ContentProvider 中数据变化的相关介绍

    全部评论我要评论

  • 取消发布评论发送
  • 1/1

    PHP中文网