Java中hashcode,equals和==


hashcode方法返回该对象的哈希码值。

hashCode()方法可以用来来提高Map里面的搜索效率的,Map会根据不同的hashCode()来放在不同的位置,Map在搜索一个对象的时候先通过hashCode()找到相应的位置,然后再根据equals()方法判断这个位置上的对象与当前要插入的对象是不是同一个。若两个对象equals相等,但不在一个区间,根本没有机会进行比较,会被认为是不同的对象。

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

1、如果两个对象相同,那么它们的hashCode值一定要相同,也告诉我们重写equals方法,一定要重写hashCode方法;

2、如果两个对象的hashCode相同,它们并不一定相同

hashCode()和equals()定义在Object类中,这个类是所有java类的基类,所以所有的java类都继承这两个方法。

注意到hashCode方法前面有个native的修饰符,这表示hashCode方法是由非java语言实现的,说明是一个本地方法,它的实现是根据本地机器相关的。具体的方法实现在外部,返回内存对象的地址。

/**
    * Returns a hash code value for the object. This method is
    * supported for the benefit of hashtables such as those provided by
    * <code>java.util.Hashtable</code>.
    * <p>
    * The general contract of <code>hashCode</code> is:
    * <ul>
    * <li>Whenever it is invoked on the same object more than once during
    *    an execution of a Java application, the <tt>hashCode</tt> method
    *    must consistently return the same integer, provided no information
    *    used in <tt>equals</tt> comparisons on the object is modified.
    *    This integer need not remain consistent from one execution of an
    *    application to another execution of the same application.
    * <li>If two objects are equal according to the <tt>equals(Object)</tt>
    *    method, then calling the <code>hashCode</code> method on each of
    *    the two objects must produce the same integer result.
    * <li>It is <em>not</em> required that if two objects are unequal
    *    according to the {@link java.lang.Object#equals(java.lang.Object)}
    *    method, then calling the <tt>hashCode</tt> 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 hashtables.
    * </ul>
    * <p>
    * As much as is reasonably practical, the hashCode method defined by
    * class <tt>Object</tt> does return distinct integers for distinct
    * objects. (This is typically implemented by converting the internal
    * address of the object into an integer, but this implementation
    * technique is not required by the
    * Java<font size="-2"><sup>TM</sup></font> programming language.)
    *
    * @return  a hash code value for this object.
    * @see    java.lang.Object#equals(java.lang.Object)
    * @see    java.util.Hashtable
    */
    public native int hashCode();

在java类中可以重写这两个方法,下面是String类中这两个类的实现。

/**
    * Compares this string to the specified object.  The result is {@code
    * true} if and only if the argument is not {@code null} and is a {@code
    * String} object that represents the same sequence of characters as this
    * object.
    *
    * @param  anObject
    *        The object to compare this {@code String} against
    *
    * @return  {@code true} if the given object represents a {@code String}
    *          equivalent to this string, {@code false} otherwise
    *
    * @see  #compareTo(String)
    * @see  #equalsIgnoreCase(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;
    }

 

 

/**
    * Returns a hash code for this string. The hash code for a
    * <code>String</code> object is computed as
    * <blockquote><pre>
    * s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
    * </pre></blockquote>
    * using <code>int</code> arithmetic, where <code>s[i]</code> is the
    * <i>i</i>th character of the string, <code>n</code> is the length of
    * the string, and <code>^</code> indicates exponentiation.
    * (The hash value of the empty string is zero.)
    *
    * @return  a hash code value for this object.
    */
    public int hashCode() {
    int h = hash;
        int len = count;
    if (h == 0 && len > 0) {
        int off = offset;
        char val[] = value;

            for (int i = 0; i < len; i++) {
                h = 31*h + val[off++];
            }
            hash = h;
        }
        return h;
    }

String类中equals是基于内容的比较,而不是基于地址的比较。

Java语言对equals()的要求如下,这些要求是必须遵循的:

• 对称性:如果x.equals(y)返回是“true”,那么y.equals(x)也应该返回是“true”。

• 反射性:x.equals(x)必须返回是“true”。

• 类推性:如果x.equals(y)返回是“true”,而且y.equals(z)返回是“true”,那么z.equals(x)也应该返回是“true”。

• 还有一致性:如果x.equals(y)返回是“true”,只要x和y内容一直不变,不管你重复x.equals(y)多少次,返回都是“true”。

• 任何情况下,x.equals(null),永远返回是“false”;x.equals(和x不同类型的对象)永远返回是“false”。

以上这五点是重写equals()方法时,必须遵守的准则,如果违反会出现意想不到的结果,请大家一定要遵守。

 

java中==、equals()、hashCode()都和对象的比较有关,在java中这三者各有什么用处呢,即java中为什么需要设计这三种对象的比较方法呢?

1.关于==

==是容易理解的。java设计java就是要比较两个对象是不是同一个对象。

对于引用变量而言,比较的时候两个引用变量引用的是不是同一个对象,即比较的是两个引用中存储的对象地址是不是一样的。

对于基本数据类型而言,比较的就是两个数据是不是相等,没什么歧义。

由于对于基本数据类型而言,没有方法,所以不存在equal()和hashCode()的问题,下面的讨论都是针对引用类型而言的。

2.关于equals()

为什么java会设计equals()方法?

==比较的是两个对象是否是同一个对象,这并不能满足很多需求。有时候当两个对象不==的时候,我们仍然会认为两者是“相等”的,比如对于String对象,当两个对象的字符串序列是一直的,我们就认为他们是“相等”的。对于这样的需求,需要equals()来实现。对于有这种需求的对象的类,重写其equals()方法便可,具体的“相等”逻辑可以根据需要自己定义。

需要注意的地方

Object中equals()的默认实现是比较两个对象是不是==,即其和==的效果是相同的。

java提供的某些类已经重写了equals()方法。自己写的类,如果需要实现自己的“相等”逻辑,需要重写equals()方法。

3.关于hashCode()

为什么会设计hashCode()方法?

hashCode()方法返回的就是一个数值,我们称之为hashCode吧。从方法的名称上就可以看出,其目的是生成一个hash码。hash码的主要用途就是在对对象进行散列的时候作为key输入,据此很容易推断出,我们需要每个对象的hash码尽可能不同,这样才能保证散列的存取性能。事实上,Object类提供的默认实现确实保证每个对象的hash码不同(在对象的内存地址基础上经过特定算法返回一个hash码)。

分析到这个地方,看似没什么问题,三者的作用很清晰,好像他们之间也没什么关系。在java的规范上,hashCode()方法和equals()方法确实可以没有关系。

但是!!!!!!!!有一个问题。

问题如下:对于集合类HashSet、HashMap等和hash有关的类(��HashSet为例),是通过hash算法来散列对象的。对HashSet而言,存入对象的流程为:根据对象的hash码,经过hash算法,找到对象应该存放的位置,如果该位置为空,则将对象存入该位置;如果该位置不为空,则使用equals()比较该位置的对象和将要入的对象,如果两个相等,则不再插入,如果不相等,根据hash冲突解决算法将对象插入其他位置。

而java规定对于HashSet判断是不是重复对象就是通过equals() 方法来完成,这就需要在两个对象equals()方法相等的时候,hash码一定相等(即hashCode()返回的值相等)。假设两个对象equals()方法相等的时候,hash码不相等,会出现equals()相等的两个对象都插入了HashSet中,这时不允许的。从而我们有了一下的结论:

结论:对于equals()相等的两个对象,其hashCode()返回的值一定相等

通过上面的分析,对于这个结论是没有异议的。结合前面关于hash码要尽可能不同的要求,现在变成了对于equals()相等的对象hash码要一定相等,而对于equals()不同的对象要尽量做到hash码不同。那么怎么才能保证这一点呢?

4.重写hashCode()

首先,如何保证“对于equals()相等的对象hash码要一定相等”。

equals()方法中对于对象的比较是通过比较对象中全部或者部分字段来完成的,这些字段集合记为集合A,如果我们计算hash码的时候,如果只是从集合A中选取部分字段或者全部字段来完成便可,因为输入相同,不管经过什么算法,输出一定相同(在方法中调用随机函数?这属于吃饱了撑的!)。如此设计便保证满足了第一个要求。

其次,对于equals()不同的对象要尽量做到hash码不同。

对于这一点的保证就是在设计一个好的算法,让不同的输入尽可能产生不同的输出。

下面就详细介绍一下如何设计这个算法。这个算法是有现成的参考的,算法的具体步骤就是:

[1]把某个非零常数值(一般取素数),例如17,保存在int变量result中;

[2]对于对象中每一个关键域f(指equals方法中考虑的每一个域):

[2.1]boolean型,计算(f ? 0 : 1);

[2.2]byte,char,short型,计算(int)f;

[2.3]long型,计算(int) (f ^ (f>>>32));

[2.4]float型,计算Float.floatToIntBits(afloat);

[2.5]double型,计算Double.doubleToLongBits(adouble)得到一个long,再执行[2.3];

[2.6]对象引用,递归调用它的hashCode方法;

[2.7]数组域,对其中每个元素调用它的hashCode方法。

[3]将上面计算得到的散列码保存到int变量c,然后执行 result=37*result+c;

[4]返回result。

其实其思路就是:先去一个基数,然后对于equals()中考虑的每一个域,先转换成整数,再执行result=37*result+c;

本文永久更新链接地址

相关内容