深入理解HashCode和equals
hashCode和equals方法
JDK version:1.8.0_171
Object类位于Java的顶层,是Java所有类的父类。任何对象,包括数组,都实现了该类的方法。首先针对Object类中的两个方法equals()和hashCode()方法进行讲解。
1. Object类hashCode和equals方法
hashCode方法返回对象的哈希值,实现本方法主要是为了支持hash表(比如HashMap)
对于hashCode的规定如下:
- 在Java应用程序的一次执行过程中,如果一个对象在equals()方法进行比较所使用到的信息没有修改,那么该对象的hashCode()不管调用多少次,始终返回相同的hash值。但多次运行Java应用程序时,同一个对象的hashCode()方法的返回结果可以不同。
- 如果x.equals(y)返回true,那么x和y的hashCode()方法应该返回相同的hash值。
- 如果x.equals(y)返回false,那么x和y的hashCode()方法可以返回相同的hash值,但是我们必须意识到对于"不相等"的两个对象返回相同的hash值,这可能会降低hash表的性能。
public native int hashCode();
在合理可行的范围内,Object类实现的hashCode方法对于不同的对象返回不同的整数。
修饰符native表明该方法是原生方法,由操作系统实现,Java只负责调用,不同平台可能会有不同的实现,这里可以理解为Object类的hashCode()返回的hash值是利用内存地址经过算法得来的(本机对于使用集成IDE实测,同一个程序中的同一个对象object,其hashCode返回始终相同,命令行多次运行结果也相同,但IDE和命令行对于同一程序中的object的hashCode返回不同,原因不太懂)。
equals方法表明与其他对象是否相等,实现了非空对象 引用的等价关系:
- 自反性:对于任何非空引用值x,x.equals(x)应返回true;
- 对称性:对于任何非空引用值x和y,x.equals(y)返回true当且仅当y.equals(x)返回true;
- 传递性:对于任何非空引用值x,y和z,如果x.equals(y)返回true且y.equals(z)返回true,则x.equals(z)也返回true;
- 一致性:对于任何非空引用值x和y,如果不改变equals比较所使用到的信息,则不管调用x.equals(y)多少次,始终返回true或者false;
- 对于非空引用值x,x.equals(null)应返回false。
Object类实现的equals方法实现了对象间最具辨别性的等价关系,即对于任何非空引用值x和呀,当且仅当x和y引用的是同一个对象(即x == y,直接比较x和y在内存中的地址,这往往没什么用)。
public boolean equals(Object obj) {
//即直接比较内存地址
return (this == obj);
}
当重写equals()方法时往往需要修改hashCode()方法,来保证"相等"的两个对象具有相同的hash值。
2.Java包装类中hashCode和equals方法
Java中的8种包装数据类型均重写了这两个方法,下面看一些实现。
- Integer类的hashCode直接返回对应的值value:
@Override
public static int hashCode(int value) {
return value;
}
equals直接判断数值是否相等
public boolean equals(Object obj) {
if (obj instanceof Integer) {
return value == ((Integer)obj).intValue();
}
return false;
}
- Double类的hashCode,这里doubleToLongBits函数中使用到了原生函数doubleToRawLongBits(),简单理解是通过bit来计算的,而位运算符和还有无符号右移保证返回为正数:
public static int hashCode(double value) {
long bits = doubleToLongBits(value);
return (int)(bits ^ (bits >>> 32));
}
equals方法
public boolean equals(Object obj) {
return (obj instanceof Double)
&& (doubleToLongBits(((Double)obj).value) ==
doubleToLongBits(value));
}
- Character类hashCode没有重写,继承Object类:
public final int hashCode() {
return super.hashCode();
}
Character character1 = new Character('a');
Character character2 = new Character('b');
System.out.println("a:" + character1.hashCode());
System.out.println("b:" + character2.hashCode());
但输出如下所示:这表明hashCode返回对应的ASCII码(这里有点疑问,明明是Object的hashCode,却返回了ASCII码,不太理解???)
和Object类一样,equals()也是直接判断内存地址是否相等
public final boolean equals(Object obj) {
return (this == obj);
}
- String类的hashCode则是通过函数h = 31 * h + val[i],这里的val是字符串的字符数组,相当于是根据每个字符的ASCII码计算的:
@Override
public int hashCode() {
//hash默认为0
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
equals则是根据字符串数组对应位置的字符均相同来判断字符串是否相等
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;
}
3.自定义hashCode()和equals()
定义一个Student类,其中id可唯一标记一个学生
public class Student {
//唯一id
private Integer id;
private String username;
public Student(Integer id, String username) {
this.id = id;
this.username = username;
}
}
Student student1 = new Student(1, "Zhang San");
Student student2 = new Student(2, "Li Si");
Student student3 = new Student(1, "Zhang San");
System.out.println(student1.equals(student2));
System.out.println(student1.equals(student3));
由于没有重写equals方法,此时equals直接根据内存地址相等来判断是否为同一个学生,第二个输出为false,与我们所期望的输出不一致。下面是重写的equals方法:
@Override
public boolean equals(Object o) {
if (this == o){
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Student student = (Student) o;
return Objects.equals(id, student.id);
}
此时可以看到equals结果输出符合期望输出,student1和student3就是同一个人,然而没有重写hashCode(),这导致student1和student3的hash值不一样,这违背了hashCode的原则:凡是equals()判断相等的两个对象其hash值必须相同。
这里由于id具备唯一性,所以可以将id直接作为hash值
@Override
public int hashCode() {
return id;
}
在重写了equals()方法后,可能需要重写hashCode()。我们只需要记住这一点:两个对象如果相等,其hashCode也必定相等。(关于HashMap等的介绍留作下篇吧)