Effective Java读书笔记、感悟——2.2 其余Object通用方法

来源:互联网 发布:狙击精英4 知乎 编辑:程序博客网 时间:2024/05/02 00:21

最近一直在忙着完成作业,看书和写博客的进度大大受到折扣。假期回去把最近Nachos的实验重新做一遍,好好总结、测试过后写份博客,中间还是学到了很多东西,虽然学不到什么API的使用。不多说了,继续Java,这才是正道(要遭批斗的言论)。这次的博客中有很多问题都没有注意到,只是做了简单的笔记记录和一些小地方的注释,着重说明了hashCode,尤其clone没有更深入的探究,但是使用过程中有时候只是简单的clone,所以尽量也使用的时候详细的阅读以下该类的clone注释(还有个原因是clone使用需要谨慎,书中提出了推荐的方法,由于最近时间紧张也就没有选择深入探究clone)。

二:覆盖equals时总要覆盖hashCode

Object规范中提到:(非原文,仅理解和摘要)

à执行期间对象的equals比较操作用到的信息没有被修改,那么调用多次hashCode应该始终返回同一个整数,多次运行可以不同。

à两个对象根据equals比较相等,那么他们的hashCode返回相同的值。

àequals不等,hashCode不一定不等,但是给完全不同的对象产生不同的散列码提高性能

如果没有覆盖hashCode而违反的关键约定是第二条。

如果试图和HashMap(在应用程序中被非常广泛的用到)、HashSet、Hashtable一起使用作为key(作为key之后会根据key产生的hashcode进行散列,如果很多不同的对象返回了相同的key就将散列表变成了很少的长链表,降低性能),不保证hashCode被覆盖将带来问题。

书中提到的散列函数(可以在其他自己需要构造散列表的时候使用):

1、把某个非零的常数值,比如说17,保存在一个名为result的int类型的变量中。

2、对于对象中每个关键域f(指equals方法中涉及的每个域),完成以下步骤:

a. 为该域计算int类型的散列码c:

i. 如果该域是boolean类型,则计算(f ? 1 : 0)。

ii. 如果该域是byte、short、char或者int类型,则计算(int)f。

iii. 如果该域是long类型,则计算(int)(f ^ (f >>> 32))。

iv. 如果该域是float类型,则计算Float.floatToIntBits(f)。

v. 如果该域是double类型,则计算Double.doubleToLongBits(f),然后照2.a.iii。

vi. 如果该域是一个对象引用,并且该类的equals方法通过递归地调用equals方法来比较这个域,则同样为这个域递归地调用hashCode。如果需要更复杂的比较,则为这个域计算一个范式,然后针对这个范式调用hashCode。如果这个域的值为null,则返回0。

vii. 如果该域是一个数组,则需要把每一个元素当做单独的域来处理,也就是说,递归地应用上述规则,对每个重要的元素计算一个散列码,然后根据步骤2.b中的做法把这些散列值组合起来。如果数组域中的每个元素都很重要,可以利用发行版本1.5中增加的其中一个Arrays.hashCode方法。

b. 按照下面的公式,把步骤2.a中计算得到的散列码c合并到result中:result = 31 * result + c ;

3、返回result。

4、写完了hashCode方法之后,问问自己“相等的实例是否都具有相等的散列码”。

之所以采用31是因为它是一个奇素数。如果乘数是偶数,并且惩罚溢出的话信息会丢失,因为与2相乘等于移位运算。使用素数的好处并不明显大,但是习惯使用素数计算散列结果。31还有一个很好的特性,即用移位和减法来代替乘法可以得到更好的性能:31*i==(i<<5)-I。现代的VM可以自动完成这种优化。合理利用移位操作提高效率是好的编程习惯。

这里先从外围,举最常用的Java的HashMap来说明一下HashCode的作用:

下面是put方法,其中是根据用户的hashCode产生的值的基础上做了一次散列计算。用户散列函数效果太差会直接影响到HashMap的使用性能。(尤其我们经常会将自己编写的Bean等作为value传入,如果没有覆盖hashCode)

原创粉丝点击