【Java】equals()与hashCode()方法详解 (转)

java.lang.Object类中有两个很是重要的方法:html

1
2
public  boolean  equals(Object obj)
public  int  hashCode()

Object类是类继承结构的基础,因此是每个类的父类。全部的对象,包括数组,都实现了在Object类中定义的方法。java

equals()方法详解

equals()方法是用来判断其余的对象是否和该对象相等.程序员

  equals()方法在object类中定义以下: 算法

public boolean equals(Object obj) {  
    return (this == obj);  
}  

很明显是对两个对象的地址值进行的比较(即比较引用是否相同)。可是咱们知道,String 、Math、Integer、Double等这些封装类在使用equals()方法时,已经覆盖了object类的equals()方法。api

  好比在String类中以下:数组

复制代码
public boolean equals(Object anObject) {  
    if (this == anObject) {  
        return true;  
    }  
    if (anObject instanceof String) {  
        String anotherString = (String)anObject;  
        int n = count;  
        if (n == anotherString.count) {  
            char v1[] = value;  
            char v2[] = anotherString.value;  
            int i = offset;  
            int j = anotherString.offset;  
            while (n– != 0) {  
                if (v1[i++] != v2[j++])  
                    return false;  
            }  
            return true;  
        }  
    }  
    return false;  
}  
复制代码

很明显,这是进行的内容比较,而已经再也不是地址的比较。依次类推Math、Integer、Double等这些类都是重写了equals()方法的,从而进行的是内容的比较。固然,基本类型是进行值的比较。oracle

它的性质有:框架

  • 自反性(reflexive)。对于任意不为null的引用值x,x.equals(x)必定是trueide

  • 对称性(symmetric)。对于任意不为null的引用值xy,当且仅当x.equals(y)true时,y.equals(x)也是truepost

  • 传递性(transitive)。对于任意不为null的引用值xyz,若是x.equals(y)true,同时y.equals(z)true,那么x.equals(z)必定是true

  • 一致性(consistent)。对于任意不为null的引用值xy,若是用于equals比较的对象信息没有被修改的话,屡次调用时x.equals(y)要么一致地返回true要么一致地返回false

  • 对于任意不为null的引用值xx.equals(null)返回false

对于Object类来讲,equals()方法在对象上实现的是差异可能性最大的等价关系,即,对于任意非null的引用值xy,当且仅当xy引用的是同一个对象,该方法才会返回true

须要注意的是当equals()方法被override时,hashCode()也要被override。按照通常hashCode()方法的实现来讲,相等的对象,它们的hash code必定相等。

hashcode() 方法详解

hashCode()方法给对象返回一个hash code值。这个方法被用于hash tables,例如HashMap。

它的性质是:

  • 在一个Java应用的执行期间,若是一个对象提供给equals作比较的信息没有被修改的话,该对象屡次调用hashCode()方法,该方法必须始终如一返回同一个integer。

  • 若是两个对象根据equals(Object)方法是相等的,那么调用两者各自的hashCode()方法必须产生同一个integer结果。

  • 并不要求根据equals(java.lang.Object)方法不相等的两个对象,调用两者各自的hashCode()方法必须产生不一样的integer结果。然而,程序员应该意识到对于不一样的对象产生不一样的integer结果,有可能会提升hash table的性能。

大量的实践代表,由Object类定义的hashCode()方法对于不一样的对象返回不一样的integer。

在object类中,hashCode定义以下:

public native int hashCode();

 说明是一个本地方法,它的实现是根据本地机器相关的。固然咱们能够在本身写的类中覆盖hashcode()方法,好比String、Integer、Double等这些类都是覆盖了hashcode()方法的。例如在String类中定义的hashcode()方法以下:

复制代码
public int hashCode() {  
    int h = hash;  
    if (h == 0) {  
        int off = offset;  
        char val[] = value;  
        int len = count;  
  
        for (int i = 0; i < len; i++) {  
            h = 31 * h + val[off++];  
        }  
        hash = h;  
    }  
    return h;  
}  
复制代码

解释一下这个程序(String的API中写到):s[0]*31^(n-1) + s[1]*31^(n-2) + … + s[n-1]
      使用 int 算法,这里 s[i] 是字符串的第 i 个字符,n 是字符串的长度,^ 表示求幂(空字符串的哈希码为 0)。

       想要弄明白hashCode的做用,必需要先知道Java中的集合。  
       总的来讲,Java中的集合(Collection)有两类,一类是List,再有一类是Set。前者集合内的元素是有序的,元素能够重复;后者元素无序,但元素不可重复。这里就引出一个问题:要想保证元素不重复,可两个元素是否重复应该依据什么来判断呢?
        这就是Object.equals方法了。可是,若是每增长一个元素就检查一次,那么当元素不少时,后添加到集合中的元素比较的次数就很是多了。也就是说,若是集合中如今已经有1000个元素,那么第1001个元素加入集合时,它就要调用1000次equals方法。这显然会大大下降效率。   
       因而,Java采用了哈希表的原理。哈希(Hash)其实是我的名,因为他提出一哈希算法的概念,因此就以他的名字命名了。哈希算法也称为散列算法,是将数据依特定算法直接指定到一个地址上,初学者能够简单理解,hashCode方法实际上返回的就是对象存储的物理地址(实际可能并非)。  
       这样一来,当集合要添加新的元素时,先调用这个元素的hashCode方法,就一会儿能定位到它应该放置的物理位置上。若是这个位置上没有元素,它就能够直接存储在这个位置上,不用再进行任何比较了;若是这个位置上已经有元素了,就调用它的equals方法与新元素进行比较,相同的话就不存了,不相同就散列其它的地址。因此这里存在一个冲突解决的问题。这样一来实际调用equals方法的次数就大大下降了,几乎只须要一两次。  

 简而言之,在集合查找时,hashcode能大大下降对象比较次数,提升查找效率!

Java对象的eqauls方法和hashCode方法是这样规定的:

一、相等(相同)的对象必须具备相等的哈希码(或者散列码)。

二、若是两个对象的hashCode相同,它们并不必定相同。

 

 如下是Object对象API关于equal方法和hashCode方法的说明:

  • If two objects are equal according to the equals(Object) method, then calling the hashCode method on each of the two objects must produce the same integer result.
  • It is not required that if two objects are unequal according to the equals(java.lang.Object) method, then calling the hashCode method on each of the two objects must produce distinct integer results. However, the programmer should be aware that producing distinct integer results for unequal objects may improve the performance of hash tables.
  • 以上API说明是对以前2点的官方详细说明

关于第一点,相等(相同)的对象必须具备相等的哈希码(或者散列码),为何?

 想象一下,假如两个Java对象A和B,A和B相等(eqauls结果为true),但A和B的哈希码不一样,则A和B存入HashMap时的哈希码计算获得的HashMap内部数组位置索引可能不一样,那么A和B颇有可能容许同时存入HashMap,显然相等/相同的元素是不容许同时存入HashMap,HashMap不容许存放重复元素。

 

 关于第二点,两个对象的hashCode相同,它们并不必定相同

 也就是说,不一样对象的hashCode可能相同;假如两个Java对象A和B,A和B不相等(eqauls结果为false),但A和B的哈希码相等,将A和B都存入HashMap时会发生哈希冲突,也就是A和B存放在HashMap内部数组的位置索引相同这时HashMap会在该位置创建一个连接表,将A和B串起来放在该位置,显然,该状况不违反HashMap的使用原则,是容许的。固然,哈希冲突越少越好,尽可能采用好的哈希算法以免哈希冲突。

 因此,Java对于eqauls方法和hashCode方法是这样规定的:     

   1.若是两个对象相同,那么它们的hashCode值必定要相同;

      2.若是两个对象的hashCode相同,它们并不必定相同(这里说的对象相同指的是用eqauls方法比较)。  
        如不按要求去作了,会发现相同的对象能够出如今Set集合中,同时,增长新元素的效率会大大降低。
      3.equals()相等的两个对象,hashcode()必定相等;equals()不相等的两个对象,却并不能证实他们的hashcode()不相等。

        换句话说,equals()方法不相等的两个对象,hashcode()有可能相等(个人理解是因为哈希码在生成的时候产生冲突形成的)。反过来,hashcode()不等,必定能推出equals()也不等;hashcode()相等,equals()可能相等,也可能不等。

        在object类中,hashcode()方法是本地方法,返回的是对象的地址值,而object类中的equals()方法比较的也是两个对象的地址值,若是equals()相等,说明两个对象地址值也相等,固然hashcode()也就相等了;在String类中,equals()返回的是两个对象内容的比较,当两个对象内容相等时,Hashcode()方法根据String类的重写代码的分析,也可知道hashcode()返回结果也会相等。以此类推,能够知道Integer、Double等封装类中通过重写的equals()和hashcode()方法也一样适合于这个原则。固然没有通过重写的类,在继承了object类的equals()和hashcode()方法后,也会遵照这个原则。

Hashset、Hashmap、Hashtable与hashcode()和equals()的密切关系

Hashset是继承Set接口,Set接口又实现Collection接口,这是层次关系。那么Hashset、Hashmap、Hashtable中的存储操做是根据什么原理来存取对象的呢?

        下面以HashSet为例进行分析,咱们都知道:在hashset中不容许出现重复对象,元素的位置也是不肯定的。在hashset中又是怎样断定元素是否重复的呢?在java的集合中,判断两个对象是否相等的规则是:
         1.判断两个对象的hashCode是否相等

             若是不相等,认为两个对象也不相等,完毕
             若是相等,转入2
           (这一点只是为了提升存储效率而要求的,其实理论上没有也能够,但若是没有,实际使用时效率会大大下降,因此咱们这里将其作为必需的。)

         2.判断两个对象用equals运算是否相等
            若是不相等,认为两个对象也不相等
            若是相等,认为两个对象相等(equals()是判断两个对象是否相等的关键)
            为何是两条准则,难道用第一条不行吗?不行,由于前面已经说了,hashcode()相等时,equals()方法也可能不等,因此必须用第2条准则进行限制,才能保证加入的为非重复元素。

例1:

复制代码
 1 package com.bijian.study;
 2 
 3 import java.util.HashSet;
 4 import java.util.Iterator;
 5 import java.util.Set;
 6 
 7 public class HashSetTest {
 8 
 9     public static void main(String args[]) {
10         String s1 = new String("aaa");
11         String s2 = new String("aaa");
12         System.out.println(s1 == s2);
13         System.out.println(s1.equals(s2));
14         System.out.println(s1.hashCode());
15         System.out.println(s2.hashCode());
16         Set hashset = new HashSet();
17         hashset.add(s1);
18         hashset.add(s2);
19         Iterator it = hashset.iterator();
20         while (it.hasNext()) {
21             System.out.println(it.next());
22         }
23     }
24 }
复制代码

运行结果:

false
true
96321
96321
aaa

  这是由于String类已经重写了equals()方法和hashcode()方法,因此hashset认为它们是相等的对象,进行了重复添加。

例2:

复制代码
 1 package com.bijian.study;
 2 
 3 import java.util.HashSet;
 4 import java.util.Iterator;
 5 
 6 public class HashSetTest {
 7 
 8     public static void main(String[] args) {
 9         HashSet hs = new HashSet();
10         hs.add(new Student(1, "zhangsan"));
11         hs.add(new Student(2, "lisi"));
12         hs.add(new Student(3, "wangwu"));
13         hs.add(new Student(1, "zhangsan"));
14 
15         Iterator it = hs.iterator();
16         while (it.hasNext()) {
17             System.out.println(it.next());
18         }
19     }
20 }
21 
22 class Student {
23     int num;
24     String name;
25 
26     Student(int num, String name) {
27         this.num = num;
28         this.name = name;
29     }
30 
31     public String toString() {
32         return num + ":" + name;
33     }
34 }
复制代码

运行结果:

1:zhangsan  
3:wangwu  
2:lisi  
1:zhangsan 

为何hashset添加了相等的元素呢,这是否是和hashset的原则违背了呢?回答是:没有。由于在根据hashcode()对两次创建的new Student(1,“zhangsan”)对象进行比较时,生成的是不一样的哈希码值,因此hashset把他看成不一样的对象对待了,固然此时的equals()方法返回的值也不等。

        为何会生成不一样的哈希码值呢?上面咱们在比较s1和s2的时候不是生成了一样的哈希码吗?缘由就在于咱们本身写的Student类并无从新本身的hashcode()和equals()方法,因此在比较时,是继承的object类中的hashcode()方法,而object类中的hashcode()方法是一个本地方法,比较的是对象的地址(引用地址),使用new方法建立对象,两次生成的固然是不一样的对象了,形成的结果就是两个对象的hashcode()返回的值不同,因此Hashset会把它们看成不一样的对象对待。

        怎么解决这个问题呢?答案是:在Student类中从新hashcode()和equals()方法。

复制代码
class Student {
    int num;
    String name;

    Student(int num, String name) {
        this.num = num;
        this.name = name;
    }

    public int hashCode() {
        return num * name.hashCode();
    }

    public boolean equals(Object o) {
        Student s = (Student) o;
        return num == s.num && name.equals(s.name);
    }

    public String toString() {
        return num + ":" + name;
    }
}
复制代码

运行结果:

1:zhangsan  
3:wangwu  
2:lisi  

能够看到重复元素的问题已经消除,根据重写的方法,即使两次调用了new Student(1,"zhangsan"),咱们在得到对象的哈希码时,根据重写的方法hashcode(),得到的哈希码确定是同样的,固然根据equals()方法咱们也可判断是相同的,因此在向hashset集合中添加时把它们看成重复元素看待了。

重写equals()和hashcode()小结:

  1.重点是equals,重写hashCode只是技术要求(为了提升效率)
      2.为何要重写equals呢?由于在java的集合框架中,是经过equals来判断两个对象是否相等的
      3.在hibernate中,常用set集合来保存相关对象,而set集合是不容许重复的。在向HashSet集合中添加元素时,其实只要重写equals()这一条也能够。但当hashset中元素比较多时,或者是重写的equals()方法比较复杂时,咱们只用equals()方法进行比较判断,效率也会很是低,因此引入了hashCode()这个方法,只是为了提升效率,且这是很是有必要的。好比能够这样写:

public int hashCode(){  
   return 1; //等价于hashcode无效  
}  

这样作的效果就是在比较哈希码的时候不能进行判断,由于每一个对象返回的哈希码都是1,每次都必需要通过比较equals()方法后才能进行判断是否重复,这固然会引发效率的大大下降。 

转自:http://www.javashuo.com/article/p-pzwhpmki-bv.html

相关文章
相关标签/搜索