effective java-读书笔记-第三章 对于所有对象都通用的方法

个人博客同步发布:effective java-读书笔记-第三章 对于所有对象都通用的方法

第三章 对于所有对象都通用的方法

所有非final方法(equals、hashCode、toString、clone、finalize)都有明确的通用约定,因为它们被设计成是要被覆盖的,如果不遵守,基于散列的集合(HashMap、HashSet、HashTable)可能无法结合该类一起运作。

第8条 覆盖equals时请遵守通用约定

覆盖equals规范:

  1. 自反性(reflexive)。对于任何非null的引用值x,x.equals(x)必须返回true。
  2. 对称性(symmetric)。对于任何非null的引用值x和y,当且仅当y.equals(x)返回true时,x.equals(y)必须返回true。
  3. 传递性(transitive)。对于任何非null的引用值x、y和z,如果x.equals(y)返回true,并且y.equals(z)也返回true,那么x.equals(z)必须返回true。
  4. 一致性(consistent)。对于任何非null的引用值x和y,只有equals的比较操作在对象中所用的信息没有被修改,多次调用x.equals(y)就会一致的返回true或者false。

实现equals方法:

  1. 使用==操作符检查“参数是否为这个对象的引用”。如果是,返回true。
  2. 使用instanceof操作符检查”参数是否为正确的类型”。如果不是返回false。
  3. 把参数转换成正确的类型。转换之前用instanceof判断,确保正确。
  4. 对于该类中的每个”关键“字段,检查参数中的字段是否与该对象中对应的字段相匹配。如果这些测试检查成功,返回true,否则返回false。
  5. 当编写完equals方法,编写单元测试校验它是否是对称的、传递的、一致的。

注意:

  1. 覆盖equals方法时总要覆盖hashCode方法。
  2. 不要企图让equals方法过于智能。比如File不应该和文件链接当作相等来看待。
  3. 不要将equals方法声明中的Object对象替换为其他类型。

第9条 覆盖equals时总要覆盖hashCode

hashcode方法返回该对象的哈希码值。通常有必要重写 hashCode 方法,以维护 hashCode 方法的常规协定,该协定声明相等对象必须具有相等的哈希码。

hashCode常规协定

  1. 在应用程序执行期间,只要对象的equals方法的比较操作中所用的信息没有被修改,那么对这同一对象多次调用,hashCode方法都必须一致地返回同样的整数。从某一应用程序的一次执行到同一应用程序的另一次执行,该整数无需保持一致。
  2. 如果两个对象根据equals(Object)方法比较是相等的,那么调用两个对象中的任意一个对象上hashCode 方法都必须产生相同的整数结果。
  3. 如果两个对象根据equals(Object)方法比较是不相等的,那么调用两个对象中的任意一个对象上hashCode 方法,则不一定产生不同的整数结果。但是,程序员应该知道,为不相等的对象生成不同整数结果可以提高哈希表的性能。

对于equals()相等的两个对象,其hashCode()返回的值一定相等

重写hashCode方法

  1. 保证equals相等的对象hash码要一定相等
  2. 对于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=37
    result+c;例如:
1
2
3
4
5
6
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方法

1
2
public Obj(Obj obj);
public static Obj newInstance(Obj obj);

第12条 考虑实现Comparable接口

Comparable接口表明它的实例具有内在排序关系。

Comparable声明:

  1. 如果第一个对象小于第二个对象,则第二个对象一定大于第一个对象;如果第一个对象等于第二个对象,则第二个对象一定等于第一个对象;如果第一个对象大于第二个对象,则第二个对象一定小于第一个对象。
  2. 如果第一个对象大于第二个对象,并且第二个对象又大于第三个对象,那么第一个对象一定大于第三个对象。
  3. 在比较时被认为相等的所有对象,它们跟别的对象做比较时一定会产生同样的结果。

Comparable的声明和equals的声明类似,也遵守自反性、对称性和传递性。

强烈建议( (x.compareTo(y)==0)==(x.eauals(y) ),但这并非绝对必要。

Comparable实现

compareTo方法中的域的比较时顺序的比较,而不是等同性的比较。笔记对象引用域可以使通过递归地调用compareTo方法来实现。如果一个域没有实现Comparable接口,或者你需要一个非标准的排序关系,就可以使用一个显示的Comparator接口来代替。

1
2
3
4
5
6
7
8
9
10
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-读书笔记-第三章 对于所有对象都通用的方法


郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。