Java中的hashcode方法

  哈希表这个数据结构想必大多数人都不陌生,而且在很多地方都会利用到hash表来提高查找效率。在Java的Object类中有一个方法:

 public native int hashCode();

   hashCode是根类Obeject中的方法。

 默认情况下,Object中的hashCode() 返回对象的32位jvm内存地址。也就是说如果对象不重写该方法,则返回相应对象的32为JVM内存地址。

  为何Object类需要这样一个方法?它有什么作用呢?hashCode与equals又有什么区别与联系?下面会详细分析!

一、equals方法的作用:

 文章:http://www.dczou.com/viemall/94.html 已经详细的分析了equals的作用;

 如:String equals 方法:

   public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        if (anObject instanceof String) {
            String anotherString = (String) anObject;
            int n = value.length;
            if (n == anotherString.value.length) {
                char v1[] = value;
                char v2[] = anotherString.value;
                int i = 0;
                while (n-- != 0) {
                    if (v1[i] != v2[i])
                            return false;
                    i++;
                }
                return true;
            }
        }
        return false;
  }

   1、默认情况(没有覆盖equals方法)下equals方法都是调用Object类的equals方法,而Object的equals方法主要用于判断对象的内存地址引用是不是同一个地址(是不是同一个对象)。

   2 、要是类中覆盖了equals方法,那么就要根据具体的代码来确定equals方法的作用了,覆盖后一般都是通过对象的内容是否相等来判断对象是否相等。

   没有覆盖equals方法代码如下:

public class Student {
     private int age;
     private String name;
    public Student() {
    }
    public Student(int age, String name) {
         super();
       this.age = age;
       this.name = name;
    }
    public int getAge() {
       return age;
    }
    public String getName() {
       return name;
    }
    public void setAge(int age) {
       this.age = age;
    }
    public void setName(String name) {
       this.name = name;
    }
    public static void main(String[] args) {  
        Student stu1  = new Student(3,"张三");  
        Student stu2  = new Student(3,"张三");  
        System.out.println("stu1 == stu2 : "+(stu1 == stu2));  
        System.out.println("stu1.equals(stu2) : "+stu1.equals(stu2));  
    }  
}

运行结果:

stu1 == stu2 : false
stu1.equals(stu2) : false

 

     结果分析:Student类没有覆盖equals方法,stu1调用equals方法实际上调用的是Object的equals方法。所以采用对象内存地址是否相等来判断对象是否相等。因为是两个新对象所以对象的内存地址不相等,所以stu1.equals(stu2) 是false。

   3、我们覆盖一下equals方法(age和name属性),让Student类其通过判断对象的内容是否相等来确定对象是否相等。


    覆盖后的Student类:

public class Student {
    private int age;
    private String name;
    public Student() {
    }
    public Student(int age, String name) {
    super();
    this.age = age;
    this.name = name;
    }
    public int getAge() {
    return age;
    }
    public String getName() {
    return name;
    }
    public void setAge(int age) {
    this.age = age;
    }
    public void setName(String name) {
    this.name = name;
    }
   @Override  
    public boolean equals(Object obj) { 
       System.out.println("--------runing Override equals--"); 
        if (this == obj)  
            return true;  
        if (obj == null)  
            return false;  
        if (getClass() != obj.getClass())  
            return false;  
        Student other = (Student) obj;  
        if (age != other.age)  
            return false;  
        if (name == null) {  
            if (other.name != null)  
                return false;  
        } else if (!name.equals(other.name))  
            return false;  
        return true;  
    } 
 
    public static void main(String[] args) {  
        Student stu1  = new Student(3,"张三");  
        Student stu2  = new Student(3,"张三");  
        System.out.println("stu1 == stu2 : "+(stu1 == stu2));  
        System.out.println("stu1.equals(stu2) : "+stu1.equals(stu2));  
    }  
    
}

运行结果:

stu1 == stu2 : false
--------runing Override equals--
stu1.equals(stu2) : true

  结果分析:因为Student两个对象的age和name属性相等,而且又是通过覆盖equals方法来判断的,所示stu1.equals(stu2) 为true。

二、hashCode方法的作用:

 hashCode是根类Obeject中的方法。

 默认情况下,Object中的hashCode() 返回对象的32位jvm内存地址。也就是说如果对象不重写该方法,则返回相应对象的32为JVM内存地址。

对于包含容器类型的程序设计语言来说,基本上都会涉及到hashCode。在Java中也一样,hashCode方法的主要作用是为了配合基于散列的集合一起正常运行,这样的散列集合包括HashSet、HashMap以及HashTable。

  String类源码中重写的hashCode方法如下:

  public int hashCode() {
        int h = hash;
        if (h == 0 && value.length > 0) {
            char val[] = value;
            for (int i = 0; i < value.length; i++) {
                h = 31 * h + val[i];
            }
            hash = h;
        }
        return h;
    }

    为什么这么说呢?考虑一种情况,当向集合中插入对象时,如何判别在集合中是否已经存在该对象了?(注意:集合中不允许重复的元素存在)

也许大多数人都会想到调用equals方法来逐个进行比较,这个方法确实可行。但是如果集合中已经存在一万条数据或者更多的数据,如果采用equals方法去逐一比较,效率必然是一个问题。此时hashCode方法的作用就体现出来了,当集合要添加新的对象时,先调用这个对象的hashCode方法,得到对应的hashcode值,实际上在HashMap的具体实现中会用一个table保存已经存进去的对象的hashcode值,如果table中没有该hashcode值,它就可以直接存进去,不用再进行任何比较了;如果存在该hashcode值, 就调用它的equals方法与新元素进行比较,相同的话就不存了,不相同就散列其它的地址,所以这里存在一个冲突解决的问题,这样一来实际调用equals方法的次数就大大降低了,说通俗一点:Java中的hashCode方法就是根据一定的规则将与对象相关的信息(比如对象的存储地址,对象的字段等)映射成一个数值,这个数值称作为散列值。下面这段代码是java.util.HashMap的中put方法的具体实现: 

 

 public V put(K key, V value) {
        if (table == EMPTY_TABLE) {
            inflateTable(threshold);
        }
        if (key == null)
            return putForNullKey(value);
        int hash = hash(key);
        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方法的调用次数,从而提高程序效率,在集合中哈希表更多的是通过hashCode检索对象。

三:equals方法和hashCode方法:

在对象比较过程中有如下几种情况需要注意:

   1、如果调用equals方法得到的结果为true,则两个对象的hashcode值必定相等;

   2、如果equals方法得到的结果为false,则两个对象的hashcode值不一定不同;

   3、如果两个对象的hashcode值不等,则equals方法得到的结果必定为false;

   4、如果两个对象的hashcode值相等,则equals方法得到的结果未知。

  情况一

 

public class Student {
    private int age;
    private String name;
    public Student() {
    }
    public Student(int age, String name) {
    super();
    this.age = age;
    this.name = name;
    }
    public int getAge() {
    return age;
    }
    public String getName() {
    return name;
    }
    public void setAge(int age) {
    this.age = age;
    }
    public void setName(String name) {
    this.name = name;
    }
 @Override  
    public int hashCode() {  
        final int prime = 31;  
        int result = 1;  
        result = prime * result + age;  
        result = prime * result + ((name == null) ? 0 : name.hashCode());  
        return result;  
    }  
 
    @Override  
    public boolean equals(Object obj) {  
        if (this == obj)  
            return true;  
        if (obj == null)  
            return false;  
        if (getClass() != obj.getClass())  
            return false;  
        Student other = (Student) obj;  
        if (age != other.age)  
            return false;  
        if (name == null) {  
            if (other.name != null)  
                return false;  
        } else if (!name.equals(other.name))  
            return false;  
        return true;  
    }   
 
    public static void main(String[] args) {  
        Student stu1  = new Student(3,"张三");  
        Student stu2  = new Student(3,"张三");  
        System.out.println("stu1 == stu2 : "+(stu1 == stu2));  
        System.out.println("stu1.equals(stu2) : "+stu1.equals(stu2));  
        System.out.println("stu1  hashCode "+stu1.hashCode());
        System.out.println("stu2  hashCode "+stu1.hashCode());
    }    
}

运行结果:

stu1 == stu2 : false
stu1.equals(stu2) : true
stu1  hashCode 775943
stu2  hashCode 775943

情况二:

public class Student {
    private int age;
    private String name;
    public Student() {
    }
    public Student(int age, String name) {
    super();
    this.age = age;
    this.name = name;
    }
    public int getAge() {
    return age;
    }
    public String getName() {
    return name;
    }
    public void setAge(int age) {
    this.age = age;
    }
    public void setName(String name) {
    this.name = name;
    }
    @Override  
        public boolean equals(Object obj) { 
    return super.equals(obj);
        } 
@Override  
    public int hashCode() {  
        final int prime = 31;  
        int result = 1;  
        result = prime * result + age;  
        result = prime * result + ((name == null) ? 0 : name.hashCode());  
        return result;  
    }   
 
    public static void main(String[] args) {  
        Student stu1  = new Student(3,"张三");  
        Student stu2  = new Student(3,"张三");  
        System.out.println("stu1 == stu2 : "+(stu1 == stu2));  
        System.out.println("stu1.equals(stu2) : "+stu1.equals(stu2));  
        System.out.println("stu1  hashCode "+stu1.hashCode());
        System.out.println("stu2  hashCode "+stu1.hashCode());
    }  
}

运行结果:

stu1 == stu2 : false
stu1.equals(stu2) : false
stu1  hashCode 775943
stu2  hashCode 775943

通过上述的分析结论得出:

在重写equals方法的同时,必须重写hashCode方法, 只需要重写hashCode方法,让equals方法和hashCode方法始终在逻辑上保持一致性,如果要判断两个对象是否真正相等,必须通过equals方法。

在《Java编程思想》一书中的P495页也有同第一条类似的一段话:

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

发表评论