个人博客同步发布:effective java-读书笔记-第三章 对于所有对象都通用的方法
第三章 对于所有对象都通用的方法
所有非final方法(equals、hashCode、toString、clone、finalize)都有明确的通用约定,因为它们被设计成是要被覆盖的,如果不遵守,基于散列的集合(HashMap、HashSet、HashTable)可能无法结合该类一起运作。
第8条 覆盖equals时请遵守通用约定
覆盖equals规范:
- 自反性(reflexive)。对于任何非null的引用值x,x.equals(x)必须返回true。
- 对称性(symmetric)。对于任何非null的引用值x和y,当且仅当y.equals(x)返回true时,x.equals(y)必须返回true。
- 传递性(transitive)。对于任何非null的引用值x、y和z,如果x.equals(y)返回true,并且y.equals(z)也返回true,那么x.equals(z)必须返回true。
- 一致性(consistent)。对于任何非null的引用值x和y,只有equals的比较操作在对象中所用的信息没有被修改,多次调用x.equals(y)就会一致的返回true或者false。
实现equals方法:
- 使用==操作符检查“参数是否为这个对象的引用”。如果是,返回true。
- 使用instanceof操作符检查”参数是否为正确的类型”。如果不是返回false。
- 把参数转换成正确的类型。转换之前用instanceof判断,确保正确。
- 对于该类中的每个”关键“字段,检查参数中的字段是否与该对象中对应的字段相匹配。如果这些测试检查成功,返回true,否则返回false。
- 当编写完equals方法,编写单元测试校验它是否是对称的、传递的、一致的。
注意:
- 覆盖equals方法时总要覆盖hashCode方法。
- 不要企图让equals方法过于智能。比如File不应该和文件链接当作相等来看待。
- 不要将equals方法声明中的Object对象替换为其他类型。
第9条 覆盖equals时总要覆盖hashCode
hashcode方法返回该对象的哈希码值。通常有必要重写 hashCode 方法,以维护 hashCode 方法的常规协定,该协定声明相等对象必须具有相等的哈希码。
hashCode常规协定
- 在应用程序执行期间,只要对象的equals方法的比较操作中所用的信息没有被修改,那么对这同一对象多次调用,hashCode方法都必须一致地返回同样的整数。从某一应用程序的一次执行到同一应用程序的另一次执行,该整数无需保持一致。
- 如果两个对象根据equals(Object)方法比较是相等的,那么调用两个对象中的任意一个对象上hashCode 方法都必须产生相同的整数结果。
- 如果两个对象根据equals(Object)方法比较是不相等的,那么调用两个对象中的任意一个对象上hashCode 方法,则不一定产生不同的整数结果。但是,程序员应该知道,为不相等的对象生成不同整数结果可以提高哈希表的性能。
对于equals()相等的两个对象,其hashCode()返回的值一定相等
重写hashCode方法
- 保证equals相等的对象hash码要一定相等
- 对于equals不同的对象要尽量做到hash码不同
具体步骤就是:
[1]把某个非零常数值(一般取素数),例如17,保存在int变量result中;
[2]对于对象中每一个关键域f(指equals方法中考虑的每一个域):
[2.1]boolean型,计算(f ? 0 : 1);
[2.2]byte、char、short、int型,计算(int)f;
[2.3]long型,计算(int) (f ^ (f>>>32));
[2.4]float型,计算Float.floatToIntBits(afloat);
[2.5]double型,计算Double.doubleToLongBits(adouble)得到一个long,再执行[2.3];
[2.6]对象引用,递归调用它的hashCode方法;
[2.7]数组域,对其中每个元素调用它的hashCode方法。
[3]将上面计算得到的散列码保存到int变量c,然后执行 result=37result+c;
[4]返回result。
其实其思路就是:先取一个基数,然后对于equals()中考虑的每一个域,先转换成整数,再执行result=37result+c;例如:
123456 |
public int hashCode(){ int result = 17; result = 31*result+age; result=31*result+name.hashCode(); return result;} |
第10条 始终要覆盖toString
toString通用约定指出被返回的字符串应该是简洁的、但信息丰富,并且易于阅读的表达形式。提供好的toString方法可以使类用起来更加舒适,当对象被传递给println、pringtf、字符串联合操作+、日志打印,toString会被自动调用。
建议所有的子类都覆盖toString方法。
第11条 谨慎地覆盖clone
Cloneable接口表明这样的对象允许克隆,它没有包含任何方法,它改变了超类中受保护的方法clone的行为,如果一个类实现了Cloneable接口,Object的clone方法就返回该对象的逐层拷贝,否则就会抛出CloneNotSupportException异常。
覆盖clone方法要非常小心,如果类里面含有复杂数据类型,要进行深度复制,如果类里面有final属性,则无法进行clone,因为final属性在clone时无法再进行赋值。
建议对象不使用clone方法,而使用静态拷贝工厂或拷贝构造器替代clone方法
12 |
public Obj(Obj obj);public static Obj newInstance(Obj obj); |
第12条 考虑实现Comparable接口
Comparable接口表明它的实例具有内在排序关系。
Comparable声明:
- 如果第一个对象小于第二个对象,则第二个对象一定大于第一个对象;如果第一个对象等于第二个对象,则第二个对象一定等于第一个对象;如果第一个对象大于第二个对象,则第二个对象一定小于第一个对象。
- 如果第一个对象大于第二个对象,并且第二个对象又大于第三个对象,那么第一个对象一定大于第三个对象。
- 在比较时被认为相等的所有对象,它们跟别的对象做比较时一定会产生同样的结果。
Comparable的声明和equals的声明类似,也遵守自反性、对称性和传递性。
强烈建议( (x.compareTo(y)==0)==(x.eauals(y) ),但这并非绝对必要。
Comparable实现
compareTo方法中的域的比较时顺序的比较,而不是等同性的比较。笔记对象引用域可以使通过递归地调用compareTo方法来实现。如果一个域没有实现Comparable接口,或者你需要一个非标准的排序关系,就可以使用一个显示的Comparator接口来代替。
12345678910 |
public int compareTo(PhoneNumber pn) { int areaCodeDiff = areaCode - pn.areaCode; if (areaCodeDiff != 0) return areaCodeDiff; int prefixDiff = prefix - pn.prefix; if (prefixDiff != 0) return prefixDiff; // 下面可能会发生超出int最大值异常 return lineNumber - pn.lineNumber; } |
个人博客同步发布:effective java-读书笔记-第三章 对于所有对象都通用的方法