更好的重写hashCode方法

重写hashCode的规范

每一个重写equals方法的类中,也必须重写hashCode方法。java

若是不覆盖hashCode,会致使没法结合基于散列的集合正常工做,例如HashMap、HashSet和Hashtable等等,换句话说,实现了对的hashCode,就能够拿对象的实例做为Hash集合的Key,下面是重写hashCode的规范:程序员

  • 在应用程序执行期间,只要对象的equals方法的比较操做所用到的信息没有被修改,那么对这同一个对象调用屡次,hashCode方法都必须始终如一地返回同一个整数。在同一个应用程序的屡次执行过程当中,每次执行所返回的整数能够不一致;
  • 若是两个对象根据equals方法比较是相等的,那么调用这两个对象中任何一个对象的hashCode方法都必须产生一样的整数结果;
  • 若是两个对象根据equals方法比较是不相等的,那么调用这两个对象中任意一个对象的hashCode方法,则不必定要产生不一样的结果。可是程序员应该知道,给不相等的对象产生大相径庭的整数结果,有可能提升山列表(hash table)的性能;

相等的对象必须具备相等的散列码(hash code)。数组

两个不一样的实例在逻辑上有多是相等的(equals),可是hashCode方法返回的应该是两个不一样的随机整数,考虑下面这个PhoneNumber类,在企图与HashMap一块儿使用时,将失败:缓存

package test.ch02;

public class PhoneNumber {

    private final int areaCode;
    private final int prefix;
    private final int lineNumber;

    public PhoneNumber(int areaCode, int prefix, int lineNumber) {
        this.areaCode = areaCode;
        this.prefix = prefix;
        this.lineNumber = lineNumber;
    }

    public int getAreaCode() {
        return areaCode;
    }

    public int getPrefix() {
        return prefix;
    }

    public int getLineNumber() {
        return lineNumber;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (!(o instanceof PhoneNumber)) {
            return false;
        }
        PhoneNumber pn = (PhoneNumber) o;
        return pn.lineNumber == lineNumber && pn.prefix == prefix && pn.areaCode == areaCode;
    }

}
package test.ch02;

import java.util.HashMap;
import java.util.Map;

public class Test {

    public static void main(String[] args) {
        Map<PhoneNumber, String> m = new HashMap<>();
        m.put(new PhoneNumber(707, 867, 5309), "Jenny");
        String s = m.get(new PhoneNumber(707, 867, 5309));
        System.out.println(s); // null
    }

}

因为PhoneNumber没有重写hashCode方法,从而致使两个相等的实例具备不相等的散列码。ide

修正这个问题很简单,只须要为PhoneNumber提供一个适当的hashCode便可。函数

如何重写规范的hashCode方法

一个好的hashCode方法倾向于“为不相等的对象产生不相等的散列码”。性能

理想状况下,散列函数应该把集合中不相等的实例均匀地分布到全部可能的散列值上,能够采用以下作法:优化

  1. 把某个非零的常数值,好比17,保存在一个result的int类型变量中;
  2. 对于对象中每一个关键域f,完成如下步骤:
  • 为该域计算int类型的散列码c:
  • 若是该域是boolean,则计算(f ? 1 : 0);
  • 若是该域是byte、char、short或者int,则计算(int)f;
  • 若是该域是long,则计算(int)(f^(f>>>32));
  • 若是该域是float,则计算Float.floatToIntBits(f);
  • 若是该域是double,则计算Double.doubleToLongBits(f),在根据long计算;
  • 若是该域是一个对象引用,而且该类的equals方法经过递归地调用equals的方式来比较这个域,则一样为这个域递归地调用hashCode;
  • 若是该域是一个数组,则要把每个元素看成单独的域来处理,也可使用Arrays.hashCode方法;
  • 按照result = 31 * result + c来计算散列码;

为PhoneNumber重写一个hashCode:this

package test.ch02;

public class PhoneNumber {

    private final int areaCode;
    private final int prefix;
    private final int lineNumber;

    public PhoneNumber(int areaCode, int prefix, int lineNumber) {
        this.areaCode = areaCode;
        this.prefix = prefix;
        this.lineNumber = lineNumber;
    }

    public int getAreaCode() {
        return areaCode;
    }

    public int getPrefix() {
        return prefix;
    }

    public int getLineNumber() {
        return lineNumber;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (!(o instanceof PhoneNumber)) {
            return false;
        }
        PhoneNumber pn = (PhoneNumber) o;
        return pn.lineNumber == lineNumber && pn.prefix == prefix && pn.areaCode == areaCode;
    }

    @Override
    public int hashCode() {
        int result = 17;
        result = 31 * result + areaCode;
        result = 31 * result + prefix;
        result = 31 * result + lineNumber;
        return result;
    }

}
package test.ch02;

import java.util.HashMap;
import java.util.Map;

public class Test {

    public static void main(String[] args) {
        Map<PhoneNumber, String> m = new HashMap<>();
        m.put(new PhoneNumber(707, 867, 5309), "Jenny");
        String s = m.get(new PhoneNumber(707, 867, 5309));
        System.out.println(s); // Jenny
    }

}

优化hashCode方法

  • 在散列码的计算过程当中,能够把冗余域(redundant field)排除在外。
  • 为何选择31作散列码,是由于它是一个奇素数。31有个很好的特性,即用位移法和减法来代替乘法,能够获得更好的性能: 31 * i = (i<<5)-i。现代的VM能够自动完成这种优化;
  • 若是一个类是不可变的,而且计算散列码的开销很大,就应该考虑把散列码缓存在对象内部。或者考虑延迟初始化散列码,在第一次调用hashCode时缓存到内部;
  • 不要视图从散列码计算中排除掉一个对象的关键部分来提升性能;
相关文章
相关标签/搜索