天天看点

effective java摘抄(二)

一、覆盖equals时总要覆盖hashCode

       1、equals()返回true则hashcode必须相同;

       2、equals()返回false但hashcode未必返回不相同;

      对于hashcode

       1、相等的对象必须有相同的散列码,合法性;

       2、不相等的对象尽可能有不同的散列码,高效性;

二、实现合法高效的hashCode的简单解决方法:

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

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

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

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

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

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

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

                   v、如果该域是double类型,则计算Double.doubleToLongBits(f),然后按照步骤2.a.iii,为得到的long类型值计算散列码。

                   vi、如果该域是一个对象引用,并且该类的equals方法通过递归的调用equals的方法来比较这个域,则同样为这个域递归的调用hashCode。如果需要更复杂的比较,则为这个域计算一个“范式”,然后针对这个范式调用hashCode。如果这个域的值为null,则返回0(或者其他几个常数,但通常是0)。

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

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

       3、返回result。

       4、写完了hashCode方法之后需要编写单元测试来验证自测。

     注意:如果一个域的值可以根据参与计算的其他域值计算出来,则可以把这样的域排除在外。必须排除equals比较计算中没有用到的任何域,否则很可能违反hashCode预定的第二条。

       上述步骤1中用到一个非零的初始值,因此步骤2.a中计算的散列值为0的那些初始域,会影响到散列值。如果步骤1中的初始值为0,则整个散列值将不受这些初始域的影响,因为这些域会增加冲突的可能性,值17是任选的。-------------------智商不够,看不懂这段话,看几遍了还是看不懂,呜呜~~

        步骤2.b中的乘法部分是的散列值依赖于域的顺序,如果一个类包含多个相似的域,这样的乘法运算就会产生一个更好的散列函数。之所以选择31,是因为它是奇素数。如果乘数是偶数,并且乘法溢出的话,信息就会丢失,因为与2相乘等价于移位运算。使用素数的好处并不明显,但习惯上都使用素数计算散列结果。31有个很好的特性,即用移位和减法来代替乘法,可以得到更好的性能:31 * i == (i << 5) -i。现代的VM可以自动完成这种优化。

继续阅读