《Effect Java》第三章"对于所有对象都通用的方法”笔记
第八条:覆盖equals时请遵循通用约定
在覆盖equals时,你必须遵守它的通用约定。下面是约定内容,来自Object的规范【JAVASE6】
自反性:对于任何非null的引用值x,x.equals(x)必须返回true。也就是说一个类的实例一定是与它本身相等的,不管你怎么实现它的逻辑判断,但它的“本”不能忘。
对称性:对于任何非null的引用值x和y,当且仅当y.equals(x)返回true时,x.equals(y)必须返回true。这条比较好理解,x=1,y=1,你不能y=x而x!=y吧。
传递性:对于任何非null的引用值x、y和z,如果x.equals(y)返回true,并且y.equals(z)也返回true,那么x.equals(z)也必须返回true。显而易见。
一致性:对于任何非null的引用值x和y,只要equals的比较操作在对象中的所用的信息没有被修改,多次调用x.equals(y)就会一致地返回true,或者一致地返回false。这条显然,你不能多调用判断几次它的结果就产生变化了吧。
可以借鉴String中的equals()方法。
第9条:覆盖equals时总要覆盖hashCode
如果不这么做,就会违反Object.hashCode()的通用约定,从而导致该类无法结合所有基于散列的集合一起正常运作。例如hashMap,hashSet,hashTable。下面是Object规范
1.只要对象的equals方法操作所得到的信息没有被修改,那么hashCode方法也必须都始终如一的返回同一个整数。
2.如果两个对象equals产生的结果相同,那么hashCode产生的结果也必须相同。
3.如果两个对象equals产生不同的结果,那么hashCode可以相同也可以不同,但不同的结果有助于提高散列的性能。
实际上,要了解为什么需要重写hashCode方法,需要理解hashMap的原理,就需要去看下源码。
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
首先put数据进去的时候,会通过hash算法和key作为参数计算出一个值,这个值我们暂定叫做hash值,我们继续看。
V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
//....
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
//...
}
多余代码我已经省略了,可以看到数组下标是根据hash值来生成的(所以也能根据hash值来直接定位到数组的位置),然后会指向一个Node的内部类。这个数组其实放的是一个链表的内部类。因为hashCode有可能存在相同的(相同就会在进去链表),所以hashCode的散列程度直接影响其性能。
第十条:始终要覆盖toString
覆盖toString方法的好处在于测试的时候更直观清晰。并且toString的约定也指出:“建议所有的子类都覆盖这个方法”。
第十一条:谨慎的覆盖clone
书中是不建议自定义重写clone方法的,如果非要重写书中总结为一句话:clone方法就是一个构造器,你必须确保它不会伤害到原始的对象,并确保正确地创建被克隆对象中的约束条件。
再说一个与本条目无关的点,查看Cloneable接口实际上可以发现里面什么方法都没有,clone方法却来自Object类,继承了Cloneable接口为什么就能重写clone方法了呢?原因在于clone方法在Object类中的修饰符是protected,而Cloneable接口和Object处于同一个包下,熟悉修饰符的都知道protected的权限限定在同一个包下或者其子类。Cloneable和Object同属于一个包,Cloneable自然能继承clone方法,继承了Cloneable接口的成为了它的子类同样也就继承了clone方法。
--------------------本条摘抄于其他博客------------------
第十二条:考虑实现comparable接口