Effective Java —— 覆盖equals时总要覆盖hashCode

本文参考

本篇文章参考自《Effective Java》第三版第十一条"Always override hashCode when you override equals"

You must override hashCode in every class that overrides equals

hashCode()方法的通用约定如下:

  • When the hashCode method is invoked on an object repeatedly during an execution of an application, it must consistently return the same value, provided no information used in equals comparisons is modified. This value need not remain consistent from one execution of an application to another
  • If two objects are equal according to the equals(Object) method, then calling hashCode on the two objects must produce the same integer result
  • If two objects are unequal according to the equals(Object) method, it is not required that calling hashCode on each of the objects must produce distinct results. However, the programmer should be aware that producing distinct results for unequal objects may improve the performance of hash tables

若未重载hashCode()方法,只是继承自Object,即使"值类"的实例"逻辑相等",也会在HashMap和HashSet集合中表现出不同,下面是原文的例子:

Map<PhoneNumber, String> m = new HashMap<>();
m.put(new PhoneNumber(707, 867, 5309), "Jenny");

若PhoneNumber类未重载hashCode()方法,便无法使用m.get()方法获取键new PhoneNumber(707, 867, 5309)对应的值"Jenny",因为查找的过程不仅仅只是通过equals()方法比较与某个键是否相等,还比较了各自的散列码,而Object的hashCode()方法只是将实例的内存地址转化为散列码

As much as is reasonably practical, the hashCode method defined by class Object does return distinct integers for distinct objects. This is typically implemented by converting the internal address of the object into an integer, but this implementation technique is not required by the Java™ programming language.)

 

a hash function should distribute any reasonable collection of unequal instances uniformly across all int values

下面是一种解决方法:

  • Declare an int variable named result, and initialize it to the hash code c for the first significant field in your object( a significant field is a field that affects equals comparisons, and you must exclude any fields that are not used in equals comparisons)

Do not be tempted to exclude significant fields from the hash code computation to improve performance

  • For every remaining significant field f in your object, do the following:
    • If the field is of a primitive type, compute Type.hashCode(f), where Type is the boxed primitive class corresponding to f's type
    • If the field is an object reference and this class's equals method compares the field by recursively invoking equals, recursively invoke hashCode on the field. If a more complex comparison is required, compute a "canonical representation"(范式) for this field and invoke hashCode on the canonical representation. If the value of the field is null, use 0 (or some other constant, but 0 is traditional)
    • If the field is an array, treat it as if each significant element were a separate field. That is, compute a hash code for each significant element by applying these rules recursively, and combine the values. If the array has no significant elements, use a constant, preferably not 0. If all elements are significant, use Arrays.hashCode
    • Combine the hash code c
      result = 31 * result + c; (The advantage of using a prime is less clear, but it is traditional)
  • Return result

下面是采用上述规则的hashCode()重载示例

private String reference;
private int[] array;
private int primitive;
@Override
public int hashCode() {
  // Declare an int variable named result
  // and initialize it to the first significant field in your object
  int
result = reference.hashCode();
  // If all elements are significant, use Arrays.hashCode
  // Combine the hash code c
  result = 31 * result + Arrays.hashCode(array);
  // If the field is of a primitive type, compute Type.hashCode(f)
  result = 31 * result + Integer.hashCode(primitive);
  return result;
}

另外还有一种简单的生成散列码的方式 —— Objects.hash(),但是它会引发数组的创建,以便传入数目可变的参数, 如果参数中有基本类型,还需要装箱和拆箱,建议只将这类散列函数用于不太注重性能的情况

在前面一篇关于"AutoValue"的文章中,采用的是另外一套生成hashCode()方法的规则

 

cache the hash code in the object

如果是一个不可变的类(如"值类"),并且计算散列码开销较大(如字段较多的情况),可以将散列码作为单例缓存在对象内部,视情况采用"懒汉式"还是"饿汉式",下面代码采用"懒汉式"加载

private int hashCode;

@Override
public int hashCode() {
  int result = hashCode;
  if (result != 0) {
    result = reference.hashCode();
    result = 31 * result + Arrays.hashCode(array);
    result = 31 * result + Integer.hashCode(primitive);
  }
  return result;
}

posted @ 2020-05-11 11:12  咕~咕咕  阅读(396)  评论(2编辑  收藏  举报