应用场景
在Java中常用来判断两个对象是否相等的函数有equals和hashCode方法,最常见的就是在集合容器中,例如HashSet和HashMap中,保存两个不同的对象,所以需要提供一个合理的关于equals和hashCode的配置,以使得集合具有正确的使用性质。
示例
以典型的point为测试用例,为了保证集合中不存在两个相同(内容相同)的point,所以提供了重写的hashCode和equals方法
public class t{
public static void main(String[] args){
Set<point> arr=new HashSet<point>();
point a=new point(1,2);
point b=new point(1,2);
arr.add(a);
/*
根据hashCode找到位置,非空则根据equals判断是否内容相等
*/
System.out.println("arr.contains(b): "+arr.contains(b));//返回true
arr.add(b);
Iterator<point> it=arr.iterator();
while(it.hasNext()){
/*
输出"point a",map中key的hash值和equals方法都返回true
只是替换value属性,对象节点不变
*/
point p=it.next();
if(p==b){
System.out.println("point b");
}else if(p==a){
System.out.println("point a");
}
}
}
}
class point{
private final int x;
private final int y;
public point(int x,int y){
this.x=x;
this.y=y;
}
public boolean equals(Object another){
if(this==another){
return true;
}
if(another instanceof point){
point s=(point)another;
return s.x==x&&s.y==y;
}
return false;
}
public int hashCode(){
return x+y;
}
}
返回结果:
E:\>java t
arr.contains(b): true
point a
我们知道HashSet使用的其实就是一个HashMap,所以在添加元素时,首先根据键值计算hash值,找到元素在table数组上的存储位置,然后利用equals函数判断该链表中是否内容相同的值,存在则替换值(只是替换值,对象不变)。
设计规范
所以关于equals和hashCode两个函数的要求如下:
1.equals返回相等的两个对象,hashCode返回值也要相等
2.hashCode返回相等的两个对象,equals不一定相等
第二条所说的也就是在使用HashMap中的碰撞问题,即发生碰撞则将节点放到table数组某个位置的Entry链表上(Entry节点包含一个Entry类型的next域)。
关于hashCode的返回值,由第一条内容可知,原则上要求hashCode要与equals保持一致。在Object中hashCode的方法为一个本地方法
/*
* 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<font size="-2"><sup>TM</sup></font> 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();
public boolean equals(Object obj) {
return (this == obj);
}
equals方法结果是判断两个引用变量是否相等,判断引用变量中保存的对象地址或者对象的句柄地址是否相等,即判断是否引用的是同一个对象。由hashCode的方法描述可知,返回的是由对象的地址转换的整形变量。
Object中关于对象相等的判断过于严苛,在平时使用中,需要对这两个方法进行重写,例如Integer中的equals和hashCode判断的依据直接就是包装的int值
public boolean equals(Object obj) {
if (obj instanceof Integer) {
return value == ((Integer)obj).intValue();
}
return false;
}
public int hashCode() {
return value;
}
这里需要额外注意一点就是,更改一点代码如下:
public boolean equals(point another){
if(this==another){
return true;
}
if(another instanceof point){
point s=(point)another;
return s.x==x&&s.y==y;
}
return false;
}
即将point类中“重写”的equals方法参数类型做了一点修改
返回结果:
E:\>java t
arr.contains(b): false
point b
point a
很明显此时属于方法重载,而不是重写或者覆盖。额外一句,方法名相同,参数【个数,顺序,类型】存在不同为重载,完全相同为重写/覆盖(返回值不同直接就是错误)。
因为对象的hashCode的随意性,所以hashCode值相等,equals不相等的情况很多(如果hashCode值相等,equals不等,则需要改函数了),也就是要求二中所说不一致。在map中处理碰撞的方式为建立链表,存储hash值(map中的hash值是根据hashCode的返回值又进行了一次hash运算,例如HashMap中table数组的长度为2的整数次幂,调整新的hash值与table数组长度减一,即高位全是0,低位全是1,进行与运算,使得元素随机分布在table数组中)相等的元素。
结论
上面所提到的所有关于hash值的计算,都是带有存储位置性质的计算,即为了解决对象的相等问题而添加的一种判断方式,与网络安全中所讨论的hash值属于完全不同的概念。虽然都做了相等判断,但是安全方面考虑的hash计算是指,提供一种实现能力上不可逆、不相等的哈希计算方法,即使hash值域有限,但是根据已知的hash值求出原始信息或者根据一个信息求出与之hash值相等的另一信息是不能实现的。
在普通应用中如果使用的hash值计算方式很简单的话,会存在安全风险,例如在提交表单数据时,默认将数据项存储在map表中,如果hash值的计算很简单的话,则恶意的攻击会导致map中存储的元素都在table中的一个位置上,形成一个长的链表,每次的存储会存在较大的查询性能,由散列表的O(1)变为O(n)(如果在链表过长时自动更改为红黑树,则为O(log n)),所以hash值计算方式不能设计的过于简单。
参考:http://coolshell.cn/articles/6424.html