zoukankan      html  css  js  c++  java
  • hashCode与equals

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

    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的情况,那么接下来的结果就是内存泄漏。

  • 相关阅读:
    dev中gridcontrol为列添加RepositoryItemImageComboBox内置器
    arcengine查询
    利用反射动态创建对象
    arcengine加载模板时实现比例尺与地图的同步
    ArcEngine HRESULT:0x80040215的解决方法( 转)
    Silverlight中的数据访问
    HyperLink链接到项目中的文件
    Silverlight中的数据绑定4
    自定义控件的构建(3)
    自定义控件的构建(1)
  • 原文地址:https://www.cnblogs.com/Xdwd/p/5674691.html
Copyright © 2011-2022 走看看