Java中的equals和hashCode
equals():反映的是对象或变量具体的值,可能是对象的引用,也可能是值类型的值。
hashCode():计算出对象实例的哈希码,并返回哈希码,又称为散列函数。根类Object的hashCode()方法是native方法,实现逻辑与JVM有关,有些JVM是直接返回对象的存储地址,但是大多时候并不是这样,只能说可能和存储地址有一定关联,每个Object对象的hashCode都是唯一的;当然,当对象所对应的类重写了hashCode()方法时,结果就截然不同了。
之所以有hashCode方法,是因为在批量的对象比较中,hashCode要比equals来得快,很多集合都用到了hashCode,比如HashTable。
两个obj,如果equals()相等,hashCode()一定相等。equals()相等的两个对象,hashcode()一定相等。
两个obj,如果hashCode()相等,equals()不一定相等(Hash散列值有冲突的情况,虽然概率很低)。hashcode()不等,一定能推出equals()也不等。
所以:
判断两个对象是否相等的规则是:
第一步,如果hashCode()相等,则查看第二步,否则不相等;
第二步,查看equals()是否相等,如果相等,则两obj相等,否则还是不相等。
为什么要重写equals方法?
我们先看看Object中equals方法的源码:
public boolean equals(Object obj) {
return (this == obj);
}
“==”是比较两个对象的的内存地址,所以说使用Object的equals()方法是比较两个对象的内存地址是否相等。因为Object的equal方法默认是两个对象的引用的比较,如果现在需要利用对象里面的值来判断是否相等,则需要重载equal方法。String、Double、Integer、Math这些类对equals方法改写,进行的是内容的比较。
equals重写约定
自反性: x.equals(x) 一定是true。
对null: x.equals(null) 一定是false。
对称性: x.equals(y)和y.equals(x)结果一致。
传递性:x.equals(y), 并且y.equals(z),那么x.equals(z)。
一致性:对于任何非null的引用值x和y,只要equals的比较操作在对象中所用的信息没有被修改,多次调用x.equals(y)返回结果一致;因此,equals方法里面不应该依赖任何不可靠的资源。
hashCode重写约定
在某个运行时期间,只要对象的变化不会影响equals方法的决策结果,那么,在这个期间,无论调用多少次hashCode,都必须返回同一个散列码。
通过equals调用返回true的2个对象的hashCode一定一样。
通过equasl返回false的2个对象的散列码不需要不同,也就是他们的hashCode方法的返回值允许出现相同的情况。
总结一句话:等价的(调用equals返回true)对象必须产生相同的散列码。不等价的对象,不要求产生的散列码不相同。
注:关于hashCode的约定,参考哈希表的定义,就变得很好理解。因为要同时兼顾时间空间,所以允许一定的哈希冲突,但必须保证等价对象的哈希值相等(当然哈希函数还是要尽量减少冲突的)。
为了更好地遵从上面的约定,可以如此规范:重写了euqls方法的对象必须同时重写hashCode方法。
改写equals时总是要改写hashcode
比如改写一个类的equals方法,让它去比较内容而不是引用是否相同,又不去改写hashcode方法,那么就会出现一个问题,就是我new两个内容一样的对象,但是它们的地址是不同的,equals方法得到true,但是hashcode方法依赖与地址可能就会得到false,这显然不行,所以我们必须改写hashcode方法使他根据内容判断。