Object 的方法:hashCode, equals
equals() 和 hashCode() 是 Object 类的两个方法,Object 是其他所有 Java 类的父类,所以任何 Java object 都拥有这两个方法。
Object.hashCode(), equals()
先看 Object 类的源码,如下:
/* 按这里的定义,当两个参与比较的对象引用同一个对象时,函数的返回值才为 true */
public boolean equals(Object obj) {
return (this == obj);
}
如是,下面代码片段的输出结果是什么呢?
HelloWorld001 a = new HelloWorld001();
HelloWorld001 b = new HelloWorld001();
System.out.println(a.equals(b));
System.out.println(a == b);
这是输出结果:
false
false
解析:如果 equals
方法没有重写,那么比较的内容还是对象的引用,和==
是等价的。==
返回「是否为同一个对象」的布尔值。有关双等号==
的说明,参这里(15.21 Equality Operators)。
/* 从关键字 native 可知 hashCode 函数是一个依赖 JVM 实现的函数 */
public native int hashCode();
hashCode 值和 equals() 值的各种组合,以及在集合对象中的各种现象
再看 Java API 文档中 Object 类中对这两个方法的说明:
public boolean equals(Object 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 方法的常规协定,该协定声明相等对象必须具有相等的哈希码。
参数:
obj - 要与之比较的引用对象。
返回:
如果此对象与 obj 参数相同,则返回 true;否则返回 false。
另请参见:
hashCode(), Hashtable
public int hashCode()
返回该对象的哈希码值。支持该方法是为哈希表提供一些优点,例如,java.util.Hashtable 提供的哈希表。
hashCode 的常规协定是:
- 在 Java 应用程序执行期间,在同一对象上多次调用 hashCode 方法时,必须一致地返回相同的整数,前提是对象上 equals 比较中所用的信息没有被修改。从某一应用程序的一次执行到同一应用程序的另一次执行,该整数无需保持一致。
- 如果根据 equals(Object) 方法,两个对象是相等的,那么在两个对象中的每个对象上调用 hashCode 方法都必须生成相同的整数结果。
- 以下情况不是必需的:如果根据 equals(java.lang.Object) 方法,两个对象不相等,那么在两个对象中的任一对象上调用 hashCode 方法必定会生成不同的整数结果。但是,程序员应该知道,为不相等的对象生成不同整数结果可以提高哈希表的性能。
- 实际上,由 Object 类定义的 hashCode 方法确实会针对不同的对象返回不同的整数。(这一般是通过将该对象的内部地址转换成一个整数来实现的,但是 JavaTM 编程语言不需要这种实现技巧。)
返回:
此对象的一个哈希码值。
另请参见:
equals(java.lang.Object), Hashtable
以上说明文字值得细读,其实细读之後,一个常见问题就得解了:为什么重写 equals 函数时一般都要重写 hashCode 函数呢?原因在 hashCode 方法说明中的「常规协定」第二点,也已用赭红色标出。之所以做出这样的协定,是为了逻辑上合理性:集合里不存在重复的元素——两个「相等」的元素(这里是对象<Object实例>) 应该视为同一个元素。在 Java 中,集合区别元素依赖于 hashCode,那么「相等」的元素既然视为同一元素,就应该有相同的 hashCode,否则「相等」的元素会因为不同的 hashCode 成为集合中不同的元素。
注意到 hashCode 方法说明中的「常规协定」第二点中黑体标出的「必须」二字,不是「必定」。所以你还是可以写出这样的违反协定的代码:equals 比较结果为真,但 hashCode 不同。虽然这样的代码没什么逻辑上的合理性,事实上,我们可以写出这几种情况的代码:
hashCode 是否相同?
true
false
equals() 的值
true/false
true/false
hashCode 的值是否相等,可以和 equals 无关。
对于 hashCode 方法说明中的「常规协定」第三点,现实中可能存在这样的情况:equals 函数返回 false,但 hashCode 值相等,这点在上表中也可以看出。实验显示,对于相同类型的对象,如果 hashCode 相同,但 equals 返回 false,还是可以存到一个集合中去的,说明它们被视为不同的元素。而如果此时强制把 equals 也改成相同的话,那么就只能存一个了。下表列出了 HashSet 中存放同一类型的两个对象的情况:
上表的结论,可以通过下面的代码验证,不同情况,可以通过打开或关闭注释来完成。
import java.util.HashSet;
import java.util.Set;
public class HelloWorld010 {
public static void main(String[] args) {
HelloTest h1 = new HelloTest();
HelloTest h2 = new HelloTest();
HelloTest h3 = h2;
h1.setHello("hello");
h2.setHello("world");
Set<HelloTest> set = new HashSet<HelloTest>();
set.add(h1);
set.add(h2);
set.add(h3);
System.out.println(set.size());
}
}
class HelloTest {
private String hello;
public String getHello() {
return hello;
}
public void setHello(String hello) {
this.hello = hello;
}
// @Override
// public int hashCode() {
// return 1;
// }
@Override
public boolean equals(Object obj) {
return true;
}
}
如上面的代码,运行後输出2,但如果取消对 HelloTest.hashCode() 的注释,就会输出1.
如何重写
在实际应用中,很多地方需要根据应用逻辑来重定义这两个方法,对于 equals 和 hashCode 的重写,《Java:重写equals()和hashCode() 》 (by zhangjunhd)写的非常清楚,摘录如下:
1. 设计equals()
[1]使用instanceof操作符检查「实参是否为正确的类型」。
[2]对于类中的每一个「关键域」,检查实参中的域与当前对象中对应的域值。
[2.1]对于非float和double类型的原语类型域,使用==比较;
[2.2]对于对象引用域,递归调用equals方法;
[2.3]对于float域,使用Float.floatToIntBits(afloat)转换为int,再使用==比较;
[2.4]对于double域,使用Double.doubleToLongBits(adouble) 转换为int,再使用==比较;
[2.5]对于数组域,调用Arrays.equals方法。
2. 设计hashCode()
[1]把某个非零常数值,例如17,保存在int变量result中;
[2]对于对象中每一个关键域f(指equals方法中考虑的每一个域):
[2.1]boolean型,计算(f ? 0 : 1);
[2.2]byte,char,short型,计算(int);
[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。
3. 最简单的办法
可直接用 Eclipse 自动生成。鼠标放到某个类里,右键,选择 Source -> Generate hashCode() and equals()...
其他
对于 native hashCode 的实现,是依赖于具体的 JRE 环境的,暂时还没能深入了解。日後努力!
一个很好很好的讨论:《[求助]为什么重写equals方法,一定要重写HashCode方法?》 (CSDN)