这是第2篇
二。对于所有对象都通用的方法
主要介绍如何正确地改写Object类中的非final方法。
第7条:在改写equals的时候请遵守通用的约定
改写equals方法所必须遵守的几个约定
1。自反性:X.equals(X)必须返回true
2。对称性:Y.equals(X)返回的必须和X.equals(Y)一致
3。传递性:X.equals(Y),Y.equals(Z)如果返回true的话,那么X.equals(Z)也必须返回true
4。一致性:多次调用X.equals(Y)返回的值应该是一致的
5。非空性:X.equals(null)必须返回false
这里最容易产生错误的是第2和第3条。要保证第2条你必须保证不扩大比较的对象的范围。而第3条产生的问题一般在继承的过程中,子类扩展了父类,增加了新的变量,涉及到面向对象关系理论的一个基本问题,那就是:
要想在扩展一个可实例化的类的同时,既要增加新的特征,同时还要保留equals约定,没有一个简单的方法可以做到这一点。
撰写高质量的equals方法的一些处方或者说告戒:
a.使用"=="检查引用是否为空
b.使用instanceOf检查对象的类别是否正确
c.把实参转换到正确的类型,并保证实际参数中的"关键域"与当前对象中对应的域是否匹配
d.编写完equals之后应该检查是否满足:对称性,传递性,一致性
e.当你改写equals的时候,总要改写hashCode
f.不要企图让equals方法过于聪明,加入太多等价关系比较只会让事情变糟糕
g.不要使equals方法依赖于不可靠的资源
h.不要使equals声明中的Object对象替换为具体的类型对象
不过,你可以用组合代替继承,在新的类中加入一个原有类的对象,而不是继承它,这样就不会遇到保持equals的问题了。
第8条:在改写equals时总是要改写hashCode
如果不这样做的话,就导致该类无法与所有基于散列值的集合类型一起正常工作,如HashSet,HashMap。一个理想的散列函数应该把一个集合中的不相等的对象均匀的分布到所有可能的散列值上,书中提供了一种方法接近理想状态
1.把某个非0的常整数保存在一个叫result的int型变量中
2。对于对象中的每一个关键域f完成下列步骤
a。为该域计算散列码c:
i.如果是boolean f?0:1
ii.byte,char,shor,int型 (int)f
iii.long型 (int)(f^(f>>32))
ivfloat型 Float.floattoIntBits(f)
v.double型 Double.doubletoLongBits(f)得到一个long型,然后按iii计算
vi.如果是一个对象引用可以递归调用hashCode()或者计算一个另外的“规范表示”
vii。如果是一个数组,则把每一个元素当作一个单独的域来处理
b.按公式计算散列码 result=37*result+c;
3。返回result
还一点,一定要包括该类的关键性的变量,不要试图通过排除一个对象的关键部分来获得性能的提高
第9条:总是改写toString()
这一条更多的是为了提供更有价值的信息给使用者,我很少使用这点,惭愧
第10条:谨慎地改写clone()
实现对象的深层拷贝。可以这样认为,clone()方法是另一个构造函数!要编写一个行为正确的clone()方法,特别是对于一些类的内部具有可变的域是相当困难的。首先你必须实现cloneable接口,在clone()方法中调用super.clone(),然后修改任何需要修改的变量。例如,要对上一骗文章的Stack撰写一个正确的clone方法如下
public Object clone() throws CloneNotSupportedException{
Stack result=(Stack)super.clone();
result.elements=(Object)elements.clone();
return result;
}
前提要求elements不是final.
clone()方法的改写是复杂的,甚至是危险的,还有另一种方法来提供拷贝对象的功能,那就是拷贝构造函数。这同样不是C++专有的概念。
例如public Example(Example),你传入一个同类型的对象,然后在这个构造函数中做变量的拷贝就OK了。相比于Cloneable接口的复杂度,这是一个更好的解决办法。
第11条:考虑实现Compareable接口
此接口的实现类似于equals方法,也要保持对称,传递,一致等特性,具体不再介绍,如果你需要对某个类的对象实行排序等算法或者与Collections Framework中的类打交道,你该考虑实现这个接口