参考文献:https://juejin.im/post/6844904005575901191
Java中经常会问这样的问题:为什么定义一个类,如果重写equal()就一定要重写hashCode()?
这个问题可以分成几步来看:
什么是equal?
什么是hashCode?
为什么要重写equal() ?
为什么要在重写完equal()之后重写hashCode()?
1. equal()方法是什么?
Object中,equal 的方法如下
public boolean equals(Object obj) {
return (this == obj);
}
很明显,认为相等的前提是认为两个地址相等,这种方式对于基础类型是可以的,比如int,float等
但是对于自定义的一个类型,新建的两个类,用equal地址会不同,所以无法认为是相等。
而通过Java文档中的equals()方法描述,所有要实现自己的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,那么y.equals(z)也应该返回true
- 一致性:对于两个非空对象x,y,在没有修改此对象的前提下,多次调用返回的结果应该相同
- 对于任何非空的对象x,x.equals(null)都应该返回false
2. hashCode()方法是什么?
Object中的hashCode()方法是一个本地方法,返回一个int类型的哈希值,定义不同的Obejct,返回的值都不同。
public native int hashCode();
在hashCode()方法中也有一些规约
- 如果对象在使用equals方法中进行比较的参数没有修改,那么多次调用一个对象的hashCode()方法返回的哈希值应该是相同的。
- 如果两个对象通过equals方法比较是相等的,那么要求这两个对象的hashCode方法返回的值也应该是相等的。
- 如果两个对象通过equals方法比较是不同的,那么也不要求这两个对象的hashCode方法返回的值是不相同的。但是我们应该知道对于不同对象产生不同的哈希值对于哈希表(HashMap等等)能够提高性能。
什么是native方法?
Java是无法访问到操作系统底层的,所以需要使用native(本地方法)来扩展Java程序的功能。
从使用的角度:这种方法类似于接口,不需要实现,直接就可以给出随机的返回值,如上边的hashCode()就会直接根据C++底层的结果,返回一个确定的值。
从实现原理的角度:
1. 在Java中声明native方法,然后编译。
2. javah
产生一个.h文件。
3. 写一个.cpp文件实现native导出方法,其中需要包含第二步产生的.h文件(注意其中又包含了JDK带的jni.h文件)。
4 . 将第三步的.cpp文件编译成动态链接库文件。
5. 在Java中用System.loadLibrary()
方法加载第四步产生的动态链接库文件,这个native方法就可以在Java中被访问了。
3. 为什么要重写equal? 为什么要重写hashCode?
重写equal()方法最常见的地方是Map。
Map在通过Key找Value值的时候,会调用hashCode方法,先找到这个对象是否有相同的hashCode。
如果没有,则认为这个Map中没有想找的类型。
如果有hashCode值,就接着调用equals方法,判断是否相等,这样才可以完整的取出value。
Map在存储的时候同样使用了这种方式,在一个数据存储完成之后,下一个数据会遍历Map中的Key的hashCode值:
如果hashCode相同,调用equal判断是否一样,一样则覆盖了之前的文件
如果hashCode不同,则无需判断equal,直接认为是两个不同的值,这种很容易出现一个类型存储了多个key的情况。
比如如下两段代码,最后输出的肯定是null。
原因就在于Map,没有重新写equal和hashCode,但是解开注释,就可以解决这个问题了。
package Temp1;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class Temp {
public static void main(String[] args) {
Map<CustomizedKey, Integer> data = getData();
CustomizedKey key = new CustomizedKey(10,"abc");
System.out.println("hashCode2:" + key.hashCode());
Integer integer = data.get(key);
System.out.printf(String.valueOf(integer));
}
private static Map<CustomizedKey, Integer> getData() {
Map<CustomizedKey, Integer> customizedKeyIntegerMap = new HashMap<>();
CustomizedKey key = new CustomizedKey(10,"abc");
System.out.println("hashCode1:" + key.hashCode());
customizedKeyIntegerMap.put(key, 10);
return customizedKeyIntegerMap;
}
}
package Temp1;
import javafx.scene.NodeBuilder;
import java.util.Objects;
public class CustomizedKey {
private Integer id;
private String name;
public CustomizedKey(int id, String name) {
this.id = id;
this.name = name;
}
// @Override
// public boolean equals(Object o) {
// if (this == o) return true;
// if (o == null || getClass() != o.getClass()) return false;
// CustomizedKey that = (CustomizedKey) o;
// return Objects.equals(id, that.id) &&
// Objects.equals(name, that.name);
// }
// @Override
// public int hashCode() {
// return Objects.hash(id, name);
// }
}