关注公众号回复002,里面有你想要的一切java
在每一个覆盖了 equals 方法的类中,都必须覆盖 hashCode 方法。若是不这样作的话,就会违反 hashCode 的通用约定,从而致使该类没法结合全部的给予散列的集合一块儿正常运做。这类集合包括 HashSet、HashMap,下面是Object 的通用规范:程序员
在应用程序的执行期间,只要对象的 equals 方法的比较操做所用到的信息没有被修改,那么同一个对象的屡次调用,hashCode 方法都必须返回同一个值。在一个应用程序和另外一个应用程序的执行过程当中,执行 hashCode 方法返回的值能够不相同。web
若是两个对象根据 equals 方法比较出来是相等的,那么调用这两个对象的 hashCode 方法都必须产生一样的整数结果数组
若是两个对象根据 equals 方法比较是不相等的,那么调用这两个对象的 hashCode 方法不必定要求其产生相同的结果,可是程序员应该知道,给不相等的对象产生大相径庭的整数结果,有可能提升散列表的性能。缓存
因没有覆盖 hashCode ,容易违反上面第二条的约定,即相等的对象必须拥有相同的 hashCode 散列值微信
根据类的 equals 方法,两个大相径庭的实例在逻辑上有多是相等的。可是根据 Object 的 hashCode 方法来看,它们仅仅是两个大相径庭的对象而已。所以对象的 hashCode 方法返回两个看起来是随机的整数,而不是根据第二个约定所要求的那样,返回两个相等的整数。app
例以下面这个例子:eclipse
public class PhoneNumber {
int numbersOne; int numbersTwo; int numbersThree;
public PhoneNumber(int numbersOne, int numbersTwo, int numbersThree) { this.numbersOne = numbersOne; this.numbersTwo = numbersTwo; this.numbersThree = numbersThree; }
@Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof PhoneNumber)) return false; PhoneNumber that = (PhoneNumber) o; return Objects.equals(numbersOne, that.numbersOne) && Objects.equals(numbersTwo, that.numbersTwo) && Objects.equals(numbersThree, that.numbersThree); }
public static void main(String[] args) { Map numberMap = new HashMap(); numberMap.put(new PhoneNumber(707,867,5309),"Jenny");
System.out.println(numberMap.get(new PhoneNumber(707,867,5309))); }}
此时,你可能但愿 numberMap.get(new PhoneNumber(707,867,5309))
会返回 "Jerry",但它实际上返回的是null 。这里会涉及到两个实例:第一个实例是第一次添加进入的 PhoneNumber , 它会被添加到一个桶中。由于没有重写 hashCode 方法,因此你取的时候是去另一个桶中取出来的 PhoneNumber 实例。因此天然两个实例不相等,由于 HashMap 有一项优化,能够将与每一个项相关联的散列码缓存起来,若是散列码不匹配,也就再也不去检验对象的等同性。ide
修正这个问题很是的简单,只要提供一个相等的散列码就能够了函数
@Overridepublic int hashCode() { return 42;}
上面这个 hashCode 方法是合法的。由于它确保了相等的对象老是具备一样的散列码。可是它也极为恶劣,由于每一个对象都具备相同的散列码。所以,多个具备相同散列码的 HashMap 就会彼此连在一块儿造成链表。它使得本该以线性时间运行的程序变成了以平方级的时间运行。
一个好的散列一般是 "为不相等的对象产生不相等的散列码"。这正是 hashCode 约定中的第三条含义。理想状况下,散列函数应该把集合中不相等的实例均匀地分布到全部可能的 int 值上。下面是一种简单的解决办法:
声明一个 int 变量并命名为 result,将它初始化为对象中的第一个关键域散列码 c.
对象中剩下的每个关键域 f 都完成如下步骤:
为该域计算 int 类型的散列码 c:
按照 下面的公式,把散列码 c 合并到 result 中。
result = 31 * result + c;
返回result
1)若是该域是基本类型,则计算 Type.hashCode(f)
,这里的 Type 是集装箱基本类型的类,与 f 的类型相对应
2)若是该域是一个对象引用,而且该类的 equals 方法经过递归地调用 equals 的方式来比较这个域,则一样为这个域递归地调用 hashCode 。若是为null ,则返回0
3)若是该域是一个数组,则要把每个元素看成单独的域来处理。也就是说,递归地应用上述规则,对每一个重要的元素计算一个散列码,而后根据步骤2 . b中的作法把这些散列值组合起来。若是数组域中没有重要的元素,可使用一个常量,但最好不要用0。若是数组域中的全部元素都很重要,可使用 Arrays.hashCode 方法。
写完了以后,还要进行验证,相等的实例是否具备相同的散列码,能够把上述解决办法用到 PhoneNumber 中
@Overridepublic int hashCode() { int result = Integer.hashCode(numbersOne); result = 31 * result + Integer.hashCode(numbersTwo); result = 31 * result + Integer.hashCode(numbersThree); return result;}
虽然上述给出了 hashCode 实现,但它不是最早进的。它们的质量堪比 Java 平台类库提供的散列函数。这些方法对于大多数应用程序而言已经足够了。
Objects
类有一个静态方法,它带有任意数量的对象,并为它们返回一个散列码。这个方法名为 hash 。你只须要一行代码就能够编写它的 hashCode 方法。它们的质量也是很高的,可是,它的运行速度相对慢一些,由于它们会引起数组的建立,以便传入数目可变的参数,若是参数中有基本类型,还须要装箱和拆箱。例如:
@Overridepublic int hashCode(){ return Objects.hash(numbersOne,numbersTwo,numbersThree);}
若是一个类是不可变的,而且计算 hashCode 的开销也大,那么应该把它缓存在对象内部,而不是每次请求都从新建立 hashCode。你能够选择 "延迟初始化" 的散列码。即一直到 hashCode 被第一次使用的时候进行初始化。以下:
private int hashCode;
@Overridepublic int hashCode() { int result = hashCode; if(result == 0){ result = Integer.hashCode(numbersOne); result = 31 * result + Integer.hashCode(numbersTwo); result = 31 * result + Integer.hashCode(numbersThree); hashCode = result; } return result;}
当你要重写对象的 hashCode 方法时,下面这两个约定我但愿你能遵照:
不要对 hashCode 方法的返回值作具体的规定,所以客户端没法理所固然地依赖它;这样能够为修改提供灵活性。
不要试图从散列码计算中排除掉一个对象的关键域来提升性能。
总而言之,每当覆盖 equals 方法时都必须覆盖 hashCode。不然程序将没法正确运行。hashCode 方法必须遵照 Object 规定的通用约定,而且一块儿完成必定的工做。将不相等的散列码分配给不相等的实例。这个很容易实现,可是若是不想那么费力,能够直接使用 eclipse 或者 Idea 提供的 AutoValue 自动生成就能够了。
<End>
PS:原创不易,喜欢就点个在看 or 转发朋友圈,这将是咱们最强的写做动力。

本文分享自微信公众号 - Java建设者(javajianshe)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。