运算符 ==
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!"的引用,地址不同所以不相等。
这里有个字面量的概念,而字面量是分配在常量池中的,需要了解的请戳:
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™ 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结构准备的,个人觉得有以下两方面:
- 计算下标:
看过HashMap源码的朋友应该知道,HashMap实际上是一个对象数组,而数组的下标是通过hashCode、HashMap的大小及位运算而来的(这里就不展开); - 判断冲突:
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