浅谈Java中对象的==、equals和hashCode

运算符 ==

Java中的==是比较两个对象在JVM中的地址。

@Test
public void compareAddress() {
    String str1 = "Hello World!";
    String str2 = "Hello World!";
    String str3 = new String("Hello World!");
    System.out.println(str1 == str2); // true
    System.out.println(str1 == str3); // false
}

上述代码中:

  • str1 == str2为true,是因为str1和str2都是字符串字面值“Hello World!”的引用,指向同一块地址,所以相等;
  • str1 == str3为false,是因为通过new产生的对象在堆中,str3是堆中对象的引用,而是str1是指向字符串字面值"Hello World!"的引用,地址不同所以不相等。

这里有个字面量的概念,而字面量是分配在常量池中的,需要了解的请戳:

聊聊Java的常量池

equals()

equals()是超类Object中的方法。源代码如下:

/**
 * Indicates whether some other object is "equal to" this one.
 * <p>
 * The {@code equals} method implements an equivalence relation
 * on non-null object references:
 * <ul>
 * <li>It is <i>reflexive</i>: for any non-null reference value
 *     {@code x}, {@code x.equals(x)} should return
 *     {@code true}.
 * <li>It is <i>symmetric</i>: for any non-null reference values
 *     {@code x} and {@code y}, {@code x.equals(y)}
 *     should return {@code true} if and only if
 *     {@code y.equals(x)} returns {@code true}.
 * <li>It is <i>transitive</i>: for any non-null reference values
 *     {@code x}, {@code y}, and {@code z}, if
 *     {@code x.equals(y)} returns {@code true} and
 *     {@code y.equals(z)} returns {@code true}, then
 *     {@code x.equals(z)} should return {@code true}.
 * <li>It is <i>consistent</i>: for any non-null reference values
 *     {@code x} and {@code y}, multiple invocations of
 *     {@code x.equals(y)} consistently return {@code true}
 *     or consistently return {@code false}, provided no
 *     information used in {@code equals} comparisons on the
 *     objects is modified.
 * <li>For any non-null reference value {@code x},
 *     {@code x.equals(null)} should return {@code false}.
 * </ul>
 * <p>
 * The {@code equals} method for class {@code Object} implements
 * the most discriminating possible equivalence relation on objects;
 * that is, for any non-null reference values {@code x} and
 * {@code y}, this method returns {@code true} if and only
 * if {@code x} and {@code y} refer to the same object
 * ({@code x == y} has the value {@code true}).
 * <p>
 * Note that it is generally necessary to override the {@code hashCode}
 * method whenever this method is overridden, so as to maintain the
 * general contract for the {@code hashCode} method, which states
 * that equal objects must have equal hash codes.
 * @param   obj   the reference object with which to compare.
 * @return  {@code true} if this object is the same as the obj
 *          argument; {@code false} otherwise.
 * @see     #hashCode()
 * @see     java.util.HashMap
 */
public boolean equals(Object obj) {
	return (this == obj);
}

注释翻译:

指示其他某个对象是否与此对象“相等”。
equals 方法在非空对象引用上实现相等关系:

  • 自反性:对于任何非空引用值 x,x.equals(x) 都应返回 true。
  • 对称性:对于任何非空引用值 x 和 y,当且仅当y.equals(x) 返回 true 时,x.equals(y) 才应返回 true。 * 传递性:对于任何非空引用值 x、y 和 z,如果 x.equals(y) 返回 true,并且 y.equals(z) 返回 true,那么 x.equals(z) 应返回true。
  • 一致性:对于任何非空引用值 x 和 y,多次调用 x.equals(y) 始终返回 true 或始终返回false,前提是对象上 equals 比较中所用的信息没有被修改。
  • 对于任何非空引用值 x,x.equals(null) 都应返回false。

Object 类的 equals 方法实现对象上差别可能性最大的相等关系;即,对于任何非空引用值 x 和 y,当且仅当 x 和 y 引用同一个对象时,此方法才返回 true(x == y 具有值 true)。
注意:当此方法被重写时,通常有必要重写 hashCode 方法,以维护 hashCode 方法的常规协定,该协定声明相等对象必须具有相等的哈希码。

代码非常简单,默认的equals()直接使用运算符==,也就是比较对象的地址。

Object是超类,那equals()也就是所有类都可以使用的方法,但是不是所有类都只使用了Object的equals()实现呢?

答案是否定的

String中的equals()

先看这段代码:

@Test
public void strEquals() {
    String str1 = new String("Hello World!");
    String str2 = new String("Hello World!");
    System.out.println(str1 == str2); // false
    System.out.println(str1.equals(str2)); // true
}
  • str1 == str2为false,这个上面已经说过;
  • str1.equals(str2)为ture,如果按Object的equals()应当返回false,因为两个通过new出来的对象分配在堆中,它们的引用理应不同。但是实际却和我们想的相反,可见String已经重写过equals()。

打开String的equals()源码,果不其然它已经被重写了:

/**
 * 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 = 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;
}

注释翻译:

将此字符串与指定的对象比较。当且仅当该参数不为 null,并且是与此对象表示相同字符序列的 String 对象时,结果才为 true。

那就很好理解为什么上面的代码中str1.equals(str2)为ture了,因为这两个字符串拥有相同的字符序列。

Integer中的equals()

源码:

/**
 * Compares this object to the specified object.  The result is
 * {@code true} if and only if the argument is not
 * {@code null} and is an {@code Integer} object that
 * contains the same {@code int} value as this object.
 *
 * @param   obj   the object to compare with.
 * @return  {@code true} if the objects are the same;
 *          {@code false} otherwise.
 */
public boolean equals(Object obj) {
    if (obj instanceof Integer) {
        return value == ((Integer)obj).intValue();
    }
    return false;
}

注释翻译:

比较此对象与指定对象。当且仅当参数不为 null,并且是一个与该对象包含相同 int 值的 Integer 对象时,结果为 true。

Long中equals()

源码:

/**
 * Compares this object to the specified object.  The result is
 * {@code true} if and only if the argument is not
 * {@code null} and is a {@code Long} object that
 * contains the same {@code long} value as this object.
 *
 * @param   obj   the object to compare with.
 * @return  {@code true} if the objects are the same;
 *          {@code false} otherwise.
 */
public boolean equals(Object obj) {
    if (obj instanceof Long) {
        return value == ((Long)obj).longValue();
    }
    return false;
}

注释翻译:

将此对象与指定对象进行比较。当且仅当该参数不是 null,且 Long 对象与此对象包含相同的 long 值时,结果才为 true。

从上面可以看出Long和Integer这种数值型类的equals()实现是非常相似的,类似的还有很多包装类型:Short、Byte、Float、Double…,这里就不一一熬述了。如果说==是比较对象的地址,那么equals()则是比较对象的值。

hashCode()

大家都知道,重写equals()必须也要重写hashCode(),那hashCode()究竟是干嘛的呢?我们在看equals()源码的时候也没看到调用hashCode(),它们之间感觉“八竿子打不着”呀!

实践是检验真理的唯一标准,我们直接看Object的hashCode()源码:

/**
 * Returns a hash code value for the object. This method is
 * supported for the benefit of hash tables such as those provided by
 * {@link java.util.HashMap}.
 * <p>
 * The general contract of {@code hashCode} is:
 * <ul>
 * <li>Whenever it is invoked on the same object more than once during
 *     an execution of a Java application, the {@code hashCode} method
 *     must consistently return the same integer, provided no information
 *     used in {@code equals} 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 {@code equals(Object)}
 *     method, then calling the {@code hashCode} 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 {@code 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.
 * </ul>
 * <p>
 * As much as is reasonably practical, the hashCode method defined by
 * class {@code Object} 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&trade; programming language.)
 *
 * @return  a hash code value for this object.
 * @see     java.lang.Object#equals(java.lang.Object)
 * @see     java.lang.System#identityHashCode
 */
public native int hashCode();

是个本地方法,似乎也看不出什么幺蛾子,但是注释很多,我们不妨从注释入手。

注释翻译:

返回该对象的哈希码值。支持此方法是为了提高哈希表(例如 java.util.HashMap 提供的哈希表)的性能。
hashCode 的常规协定是:

  • 在 Java 应用程序执行期间,在对同一对象多次调用 hashCode 方法时,必须一致地返回相同的整数,前提是将对象进行 equals 比较时所用的信息没有被修改。从某一应用程序的一次执行到同一应用程序的另一次执行,该整数无需保持一致。
  • 如果根据 equals(Object) 方法,两个对象是相等的,那么对这两个对象中的每个对象调用 hashCode 方法都必须生成相同的整数结果。
  • 如果根据 equals(java.lang.Object) 方法,两个对象不相等,那么对这两个对象中的任一对象上调用 hashCode 方法不要求一定生成不同的整数结果。但是,程序员应该意识到,为不相等的对象生成不同整数结果可以提高哈希表的性能。

实际上,由 Object 类定义的 hashCode 方法确实会针对不同的对象返回不同的整数。(这一般是通过将该对象的内部地址转换成一个整数来实现的,但是 Java 编程语言不需要这种实现技巧。)

从注释理解,此方法是为一些hash结构准备的,个人觉得有以下两方面:

  1. 计算下标:
    看过HashMap源码的朋友应该知道,HashMap实际上是一个对象数组,而数组的下标是通过hashCode、HashMap的大小及位运算而来的(这里就不展开);
  2. 判断冲突:
    HashMap判断哈希冲突是先判断两个key的hashCode是否相等,如果hashCode不相等就证明了key不相同,不是哈希冲突。而hashCode相等的两个key其equals结果为false,这两个key也是不相同的,但这种是哈希冲突。这时有些人可能要问了,这直接使用equals方法不就可以判断哈希冲突了吗?确实是可以,但是认真想下两个长度为n的String类型用equals判断其是否相同,就必须逐一匹配每个字符是否相同,可见其时间复杂度是O(n),无疑很低效。

JDK设计者为什么要约定hashCode这些规则,可以看出无论是算哈希数组的下标还是判断哈希冲突都是有利的,就像注释里说的提高哈希表的性能。

如果根据 equals(Object) 方法,两个对象是相等的,那么对这两个对象中的每个对象调用 hashCode 方法都必须生成相同的整数结果。

这句话同时也回答了重写equals()为什么同时也要重写hashCode()。试想一下,如果我们写了一个类,它的两个实例调用equals()是true的,但各自调用它们hashCode()却出现两个不同的值。然后我们把这个类作为HashMap的key,会发现这个HashMap用起来有很多莫名其妙的现象,那就是因为我们没有遵循JDK设计者的约定,导致HashMap没法正确地判断Key是哈希冲突还是同值覆盖。

大家感兴趣可以看一下String、Integer、Long…这些常用类是如何实现hashCode()的。

参考:

  • JDK1.8

猜你喜欢

转载自blog.csdn.net/lrh329678260/article/details/85257454