Effective Java读书笔记八

         

Item9:当覆写equals方法时,一定要覆写hashCode方法

这一条非常重要,再次提一下:覆写了equals方法就一定要覆写hashCode方法 。因为如果不这么做的话就会违反hashCode的准则,从而让基于hash的colloections——HashMap,HashSet等——的使用出现问题。

下面是hashCode方法的准则:

  • 对同一个对象调用多次hashCode方法,得到的是同一个整数,但是这个整数在同一个Application多次执行中不一定相同。
  • 如果equals方法判断两个对象相等,那么这两个对象调用hashCode得到的整数也相同。
  • 当equals判断两个对象不相等时,不用保证hashCode产生的整数也不相等;不过如果hashCode方法产生的整数也不同的话,可以提高hash表的性能

忘记覆写hashCode方法的最关键问题就是上面的第二条。

当我们覆写了equals方法后,就不能再使用Object的hashCode方法了,否则会让我们放入hash表中的key用相等的对象却无法取出。另外,也不要自作聪明,将hashCode直接覆写成返回一个固定的整数,这么做会让hash表直接退化成一个链表,让性能从O(n)直接跃到O(n2 )。

既然覆写hashCode这么重要,那么这里就记录一下覆写的一般步骤:

  1. 先创建一个整型result,任意赋一个非零整数,比如17;
  2. 对在equals方法中使用的每一个字段f,做如下操作:
    a. 计算每一个字段的hash code,记做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.ii步计算c;
        vi.  如果字段是另外一个对象的引用,那么如果覆写的equals方法中判断此字段时使用的是该字段的equals方法,那么我们只需要调用这个字段的hashCode方法即可;如果equals方法中的比较方式很复杂,那么就需要建立一个正规的形式,用来计算hashCode方法;如果这个字段是null,一般情况下,返回0就可以(其它值也行);
        vii.  如果字段类型是数组的话,跟进equals方法中使用的元素来分别计算并相加它们的hashCode,如果数组的所有元素都用在了equals方法中,那么我们可以直接调用Arrays.hashCode;
    b. 将在2.a中得到的整数c加到1的result中去:
        result = 31 * result + c
  3. 返回result;
  4. 完成后别忘了编写测试,要保证hashCode符合上面提到的准则

注意:和覆写equals方法一样,根据其它字段计算而来的字段不必用来计算hashCode,我们可以直接忽略它们。

上面步骤中,步骤1选择的17是任意的,步骤2.b的计算中选择的31是有些原因的,因为31是一个奇素数,如果是偶数的话,计算中一旦溢出了,那么值就会丢失,因为乘2相当于做右移操作。另外,31可以用位移和减法来代替,从而提高性能:31 * i == (i << 5) - i。

如果类是immutable的,并且hashCode的计算非常的复杂或者影响性能,那么我们可以考虑再类的内部增加一个字段用来保存计算好的hashCode,这样当第一次调用hashCode方法时,将计算好的结果存到这个字段中,以后每次调用hashCode方法都直接从该字段中取值,可以提高程序的性能。

最后需要强调的是,千万不要为了提高性能而在计算hashCode时省略了某些字段 。这么做可能会在计算hashCode时提高速度,但是别忘了,所有的hash表结构都会因此降低性能,最后的结果是得不偿失的。

 

Item10:尽量覆写toString方法

JavaSE6规范中提到,toString方法是用来提供简明、易读的信息的。也就是说,不覆写toString方法并没有什么问题,但是toString方法天生就是用来帮助我们了解对象的,覆写它可以让我们更容易的使用对象。

当我们把对象作为参数传递给println和printf方法,或者用来和字符串做加法连接的时候,程序会自动调用对象的toString方法。

在我们覆写toString方法时,应该包含所有对象中有用的信息。

不过需要注意的一点是关于toString的Java Doc的:在覆写toString方法时我们可以有两种选择,一种是提供一个固定的格式,并将这个格式记录到Java Doc中;另一种则不提供固定格式。如果决定提供一个固定的格式,那么就需要在Java Doc中记述清楚,同时最好可以提供一些方法让使用这个对象的人将固定格式的字符串转换为合法的对象。如果不提供固定的格式,那么就不要在Java Doc中记述和格式有关的东西,防止使用它的人依赖于这个格式后,新版本升级后无法向下兼容。但是,不管是否提供固定格式,都需要在Java Doc中解释清楚覆写toString方法的意图。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章