equals函数与hash计算

来源:互联网 发布:紫外线杀菌灯 知乎 编辑:程序博客网 时间:2024/06/06 02:53

应用场景

在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));//返回truearr.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 tarr.contains(b): truepoint 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 tarr.contains(b): falsepoint bpoint 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

0 0
原创粉丝点击