Effective Java学习笔记(二)对于所有对象都通用的方法


Object是一个具体类,但是设计他主要是为了扩展,他所有的非final方法(equals,toString,hashCode,clone,finalize)都是要被覆盖的, 并且任何一个类覆盖非final方法时都要遵守通用原则,以便其他遵守这些预定的类能够一同运作

覆盖Object非final方法的通用约定:
(1)覆盖equals时通用约定
很多覆盖equals的方式会导致严重的错误,解决此问题最好的方法就是不覆盖equals
哪些情况下不覆盖equals:
1.类的每个实例本质上都是唯一的:->类代表实体,而不是值
2.不关心类是否提供逻辑相等的测试功能
3.超类已经覆盖了equals并且超类的equals对于子类也是合适的

什么情况下覆盖equals:
类中具有自己特有的逻辑相等的概念,不同于对象相等的概念,并且父类没有覆盖equals实现期望的行为。这时需要覆盖equals。这通常属于值类的情形

覆盖equals时必须遵守的约定:
自反性:对于任何非null引用值 x,x.equals(x)必须返回true
对称性:对于任何非null引用值x,y; x.equals(y)返回true,则y.equals(x)也必须返回true 。
传递性:对于任何非null引用值x、y、z;x.equals(y)返回true,x.equals(z)返回true,则y.equals(z)也应返回true.
一致性:对于任何非null的引用值x、y;只要equals的比较操作在对象中所用的信息没有修改,就应一致的返回true或者返回false
非空性:对于任何非null的引用值x ;x.equals(null)必须返回false

实现高质量equals方法的诀窍:
1使用“==”操作符检测 参数是否为这个对象的引用,如果是 返回true
2使用“instanceof”检查 参数是否为正确的类型
3把参数转换为正确的类型
4对于类中的每个关键的字段,检查参数中的字段 是否与类中的字段相匹配

对于既不时float也不是double类型的基本类型字段,可以用“==”来比较;对于对象引用字段,可以递归的调用equals进行比较;对于float字段,可以调用Float.compare进行比较;对于double字段,可以调用Double.compare进行比较;
*注意:Float和Double基本类型要用compare进行比较 涉及NaN问题 详细内容查看笔记有趣的NaN类型

5当编写完equals方法后,应自问,是否满足:自反性、对称性、传递性、一致性。
6覆盖equals时总要覆盖hashCode
7不要让equals过于智能 过度的追求各种等价关系
8不要将equals声明中的Object替换成其他类型 @Override

(2)覆盖equals时总要覆盖hashCode
每个覆盖了equals的的类中,必须要覆盖hashCode,不然将会导致该类无法同基于hash的集合正常运作,如:HashMap、HashSet、HashTable
覆盖hashCode的通用约定:
1在应用的执行期间,只要equals比较操作所用到的字段没有修改,那么对于同一对象的多次调用,hashCode都要返回同一个值
2如果两个对象通过equals方法比较是相等的,那么这两个对象的hashCode也必须相等
3如果两个对象通过equals方法比较是不相等的,但这两个对象的hashCode不一定也是不相等的,但如果让两个不相等的对象产生两个截然不同的hashCode,可提高hash表的性能

覆盖hashCode的解决方案:
在hashCode通用约定第三条中,一个好的hashCode函数应该为不相等对象产生不同的hash值,在理想状态下,hash函数应该把集合中不相等的实例均匀的分布在所有可能的hash值上,这是比较困难的。接近于这种理想状态的解决方案可尝试用如下方法。但这不一定是最好的:
1、初始化一个int型变量(可命名为result) 初始值为一个非零的常数 如:17
2、对于equals中用到的每个字段 完成以下步骤:
a、对字段 f 计算int类型的hash值c:
i:如果该字段是boolean 则(c=f ? 1:0)
ii:如果该字段是byte、char、short或者int类型 则c=(int)f;
iii:如果该字段是long类型, 则c=(int) f^(f>>>32);
iv:如果该字段是float类型,则c=Float.floatToIntBits(f);
v:如果该字段是double类型,则Double.doubleToLongBits(f),然后 2.a.iii 计算出c;
vi:如果该字段为equals,并且递归的调用equals,则递归的调用该类的hashCode得到hash值,如果该引用为null则 c=0(或其他常数,但通常为零);
vii:如果该字段为数组,则对数组内的每个关键字段按照2.a的方法计算hash值,然后按照2.b的方法叠加,如果数组中的每个字段都是关键字段,则可以用java1.5后增加的Array.hashCode方法。
b、按照下面的公式,把步骤2.a中计算的hash值c叠加到result中:
result=31*result+c;
3、返回result;
4、编写单元测试,相等的实例是否具有相等的hash值,如果不相等 就要找出原因修改代码;

如果一个类是不可变的,并且计算hash值的开销比较大,就应该考虑把hash值缓存在对象的内部,而不是每次都去计算hash值,如果这个类的大部分对象会被用作hash key,那么就要在创建实例的时候生成hash值,否则,可以选择延迟创建hash值

*注意 不要试图在hash值的计算中去掉对象的某个关键字段来提高hashCode的性能

(3)始终要覆盖toString()
toString中要返回对象中所有值得关注的信息

(4)谨慎的覆盖clone()
所有的实现了Cloneable接口的类,都应该有一个公有的方法覆盖clone,此共有方法首先调用super.cone 然后修正任何需要修正的字段

(5)考虑实现Comparable接口
compareTo方法没有在Object中声明,它是Comparable接口中的唯一方法。compareTo方法不但可以进行等同性比较,还可以进行顺序比较,类实现了Comparable接口,表明它具有内在排序功能
实现了Comparable的对象数组排序 Arrays.sort(object);
如果编写的值类,具有非常明显的内在排序关系,比如按字母顺序、按数值顺序、按年代顺序,就应该坚决考虑实现Comparable接口

方法comareTo()
将对象A与对象B做比较,当对象A小于、等于或大于对象B时,分别返回一个负整数、零、正整数 ,如果对象B的类型无法与对象A的类型做比较 则抛出ClassCastException;

compareTo方法的通用约定:
1、确保所有的x、y满足 sgn(x.compareTo(y)) == -sgn(y.compareTo(x))。
2、确保比较关系是可传递的 x.compareTo(y)>0,y.compareTo(z)>0,那么x.compareTo(z)>0 。
3、若x.compareTo(y)==0, 则确保 sgn(z.compareTo(x)) == sgn(z.compareTo(y))。
4、建议(x.compareTo(y) == 0) == x.equals(y)。

依赖于比较关系的类包括:有序集合类 TreeSet和TreeMap 、工具类 Collections和Arrays,违反了compareTo方法约定会破坏这些依赖于比较关系的类 他们内部含有搜索和排序算法。类似于hashCode 跟hashMap的关系
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值