码迷,mamicode.com
首页 > 其他好文 > 详细

hashCode与equals

时间:2016-07-15 23:43:25      阅读:156      评论:0      收藏:0      [点我收藏+]

标签:

很多东西都是大处显积累,小处见功力,来点功力。

hashCode跟equals 相伴相生,所以要一起讨论才有意义。

在java中,hashCode方法的主要作用是为了配合基于散列的集合一起正常运行,就是说当集合中插入对象时,怎么分辨该对象是否已经存在。按照正常思路,应该是依次进行equals比较,但是其实效率不高,java中的做法是先比较hashCode,如果相同在equals比较,到这里,疑问就出来了,为什么会比较hashCode,hashCode相同的情况下为什么equals为什么还会不同?

具体来讲,hashCode就类似于数据结构里面的hash算法,通过散列的方式存放数据,那么都知道hash算法会产生冲突,于是,就会有不同的数据算出来是相同的hashCode,由于Object的hashCode方法是一个native的,C++写的,所以,直接看String重写的,

 1 /**
 2      * Returns a hash code for this string. The hash code for a
 3      * <code>String</code> object is computed as
 4      * <blockquote><pre>
 5      * s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
 6      * </pre></blockquote>
 7      * using <code>int</code> arithmetic, where <code>s[i]</code> is the
 8      * <i>i</i>th character of the string, <code>n</code> is the length of
 9      * the string, and <code>^</code> indicates exponentiation.
10      * (The hash value of the empty string is zero.)
11      *
12      * @return  a hash code value for this object.
13      */
14     public int hashCode() {
15         int h = hash;
16         if (h == 0 && value.length > 0) {
17             char val[] = value;
18 
19             for (int i = 0; i < value.length; i++) {
20                 h = 31 * h + val[i];
21             }
22             hash = h;
23         }
24         return h;
25     }

算法很简单,他并不是我们想到的内存地址,而是根据字符串内容算出来的,是一个int类型的数字,显然,不同字符串肯定会出现相同的hashCode,那么这个时候,处理冲突的方法就很关键,java中使用的是拉链法,就是每个同义词进行拉链,所以,一个hashcode会对应多个不同的对象,那么,这时候就明白了,为什么后面还会用equals继续进行判断。

再看String重写的equals方法

 1  /**
 2      * Compares this string to the specified object.  The result is {@code
 3      * true} if and only if the argument is not {@code null} and is a {@code
 4      * String} object that represents the same sequence of characters as this
 5      * object.
 6      *
 7      * @param  anObject
 8      *         The object to compare this {@code String} against
 9      *
10      * @return  {@code true} if the given object represents a {@code String}
11      *          equivalent to this string, {@code false} otherwise
12      *
13      * @see  #compareTo(String)
14      * @see  #equalsIgnoreCase(String)
15      */
16     public boolean equals(Object anObject) {
17         if (this == anObject) {
18             return true;
19         }
20         if (anObject instanceof String) {
21             String anotherString = (String) anObject;
22             int n = value.length;
23             if (n == anotherString.value.length) {
24                 char v1[] = value;
25                 char v2[] = anotherString.value;
26                 int i = 0;
27                 while (n-- != 0) {
28                     if (v1[i] != v2[i])
29                             return false;
30                     i++;
31                 }
32                 return true;
33             }
34         }
35         return false;
36     }

他是对比每个位置上的字符,当然比hashcode算法更严格。

实际来看HashMaP中是如何使用这两个的

 1     /**
 2      * Associates the specified value with the specified key in this map.
 3      * If the map previously contained a mapping for the key, the old
 4      * value is replaced.
 5      *
 6      * @param key key with which the specified value is to be associated
 7      * @param value value to be associated with the specified key
 8      * @return the previous value associated with <tt>key</tt>, or
 9      *         <tt>null</tt> if there was no mapping for <tt>key</tt>.
10      *         (A <tt>null</tt> return can also indicate that the map
11      *         previously associated <tt>null</tt> with <tt>key</tt>.)
12      */
13     public V put(K key, V value) {
14         if (table == EMPTY_TABLE) {
15             inflateTable(threshold);
16         }
17         if (key == null)
18             return putForNullKey(value);
19         int hash = hash(key);
20         int i = indexFor(hash, table.length);
21         for (Entry<K,V> e = table[i]; e != null; e = e.next) {
22             Object k;
23             if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
24                 V oldValue = e.value;
25                 e.value = value;
26                 e.recordAccess(this);
27                 return oldValue;
28             }
29         }
30 
31         modCount++;
32         addEntry(hash, key, value, i);
33         return null;
34     }

很显然,就是线比较hashCode,在比较equals,如果相同,则覆盖之前放入的(这一点很关键)。

好,基本原理说完了,就开始说关键的使用,看下面的例子

 1 package com.cxh.test1;
 2  
 3 import java.util.HashMap;
 4 import java.util.HashSet;
 5 import java.util.Set;
 6  
 7  
 8 class People{
 9     private String name;
10     private int age;
11      
12     public People(String name,int age) {
13         this.name = name;
14         this.age = age;
15     }  
16      
17     public void setAge(int age){
18         this.age = age;
19     }
20          
21     @Override
22     public boolean equals(Object obj) {
23         // TODO Auto-generated method stub
24         return this.name.equals(((People)obj).name) && this.age== ((People)obj).age;
25     }
26 }
27  
28 public class Main {
29  
30     public static void main(String[] args) {
31          
32         People p1 = new People("Jack", 12);
33         System.out.println(p1.hashCode());
34              
35         HashMap<People, Integer> hashMap = new HashMap<People, Integer>();
36         hashMap.put(p1, 1);
37          
38         System.out.println(hashMap.get(new People("Jack", 12)));
39     }
40 }

结果返回的是null,原因是什么?就是重写了equals,但是没有重写hashCode方法,这样,还没到判断equals呢,已经通过hashCode给否定了。所以,谨记一点,重写equals,一定同时重写hashCode。

上面可以重写的hashCode方法可以为:

1  @Override
2     public int hashCode() {
3         // TODO Auto-generated method stub
4         return name.hashCode()*37+age;
5     }

 

下面这段话摘自Effective Java一书:

  • 在程序执行期间,只要equals方法的比较操作用到的信息没有被修改,那么对这同一个对象调用多次,hashCode方法必须始终如一地返回同一个整数。
  • 如果两个对象根据equals方法比较是相等的,那么调用两个对象的hashCode方法必须返回相同的整数结果。
  • 如果两个对象根据equals方法比较是不等的,则hashCode方法不一定得返回不同的整数。

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

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

其实最后这句最重要的还有一点,就是调用remove的时候,如果,hashCode变了,很容易出现无法remove的情况,那么接下来的结果就是内存泄漏。

hashCode与equals

标签:

原文地址:http://www.cnblogs.com/Xdwd/p/5674691.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!