Java equals和hashCode方法规范


什么时候要重写equals方法

只当真正需要为对象提供”逻辑相等性”时才重写equals方法

equals方法需要遵守的通用约定

  1. 反射性.非空的x引用调用x.equals(x)返回true
  2. 对称性.非空的x引用调用x.equals(y)返回true时,y.equals(x)也应该返回true
  3. 传递性.非空的x,y,z引用调用x.equals(y)返回true,调用y.equals(z)返回true,那么x.equals(z)也应该返回true
  4. 一致性.非空的x,y引用调用x.equals(y)返回true,那么调用多次也应该返回true
  5. 非空的x,调用x.equals(null)应该返回false

如何编写一个正确的equals方法

  1. 使用==判断两个对象是否是同一个引用,如果是,返回true,这一步是一种优化

  2. 使用 instanceof 判断传入的对象是否是正确的类型,如果不是,返回false

  3. 对传入的对象进行转型,由于我们第2步进行了instanceof判断,这次转型是安全的

  4. 比较满足”逻辑性相等”所需要的字段,如果这些字段都相等,那么返回true.

    1. 对于非float和double类型的基本类型变量,使用==进行比较
    2. 对于对象引用,使用对应的equals方法进行比较,一些对象引用可能为空,那么应该使用Objects.equals()方法进行比较
    3. 对于float字段,使用静态的 Float.compare(float, float)进行比较.
    4. 对于double字段, 使用静态的Double.compare(double, double)进行比较.
    5. 对于数组元素,根据元素的类型按照上面的规则进行比较,如果每个元素都需要比较,那么使用Arrays.equals()方法进行比较

性能优化

  1. 如果某个字段更可能不同,那么应该提前判断,这样可能可以提前返回false.
  2. 如果某个字段判断的代价更低,也应该提前判断,这样可能可以提前返回false.
  3. 如果有某个由其他字段计算出来的字段(这个字段仅仅作为性能优化判断使用,这个字段不参与equals逻辑性相等比较),那么先判断这个字段是否相等,这样可能可以提前返回false.比如一个多边形有边和长,如果面积不相等,那么就不需要再判断边和长了

重写equals需要重写hashCode方法

根据hashCode约定,重写equals方法必须重写hashCode方法如果

HashCode方法规范

  1. 当一个应用的对象的hashCode方法被反复调用时,且对象没有进行任何修改,那么hashCode应该不变
  2. 如果x.equals(y)方法返回true,那么x和y的hashCode必须相等
  3. 如果x.equals(y)方法返回false,那么x和y的hashCode不一定要相等.但是最好使其不相等,这样可以使得在HashMap等数据结构中提升性能

为什么重写equals要重写hashCode

如果equals方法相同,hashcode不同,那么使用类似于HashMap的集合类时,那么可能将原本相同的对象Hash到不同一个桶位置,这样再次检索两个”逻辑相等”的对象就检索不到.即使hash到同一个桶中,由于在HashMap中做了优化,如果两个元素的hash值不同,那么就认为是不相等的对象.

如何写一个正确的HashCode方法

目的

一个好的哈希函数趋向于让不相等的对象返回不同的hash code,并且尽可能使其在哈希表等集合框架类中保证其数据的分散性.

步骤

  1. 为字段计算哈希值

    1. 如果字段是基础类型,那么使用对应的包装类的hashCode方法来获取哈希值
    2. 如果字段是对象引用,如果字段为null,那么通常认为哈希值为0,如果该字段对应的对象的equals方法中又递归的判断了其它字段,那么就递归调用该对象的hashCode方法.
    3. 如果字段是一个数组,对数组的每个元素计算哈希值.如果不需要数组里的元素参与计算,那么最好返回一个不是0的常数.如果所有的元素需要参与计算,那么使用Arrays.hashCode方法
  2. 第一个字段的哈希值为result,那么从第二个字段开始的哈希值称为c,那么result=result * 31 + c,依次累加

技巧

通常来说,euqals方法哪些字段参与比较,hashCode方法就哪些字段去计算哈希值

性能优化

如果一个对象是不可变的,那么可以考虑将hash值缓存起来

结论

对equals方法和hashCode方法需要注意基本的规则,但大多数工具类都可以为我们生成这个两个方法,所以实际上对于如何写正确的equals方法和hashCode方法一般很少实践.

参考资料

  • effective java item 10 Obey the general contract when overriding equalsObey the g eneral contract
  • effective java item 11 Always override hashCode when you override equals

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!