• 《Effective Java》之覆盖equals()时总要覆盖hashCode()


    1.为什么覆盖equals()时总要覆盖hashCode()?
    如果不这样做的话,就会违反了Object.hashCode()的通用约定。
    通用约定如下:

    • 只要对象的equals()方法的比较操作所用到的信息没有被修改,那么多洗调用hashCode()方法都必须返回同一个整数。
    • 如果两个对象equals()判断相等,那么其hashCode()返回值也相等。
    • 如果两个对象hashCode()返回值相等,那么equals()判断不一定相等(尽可能的满足不同对象hashCode不一致,有可能提高散列表的性能)。

    2.覆盖equals()而未覆盖hashCode的结果

    public final class PhoneNumber {
        private final short areaCode;
        private final short prefix;
        private final short lineNumber;
        public PhoneNumber(int areaCode,int prefix,int lineNumber) {
            this.areaCode=(short)areaCode;
            this.prefix=(short)prefix;
            this.lineNumber=(short)lineNumber;
        }
        @Override//实现对象的逻辑相等
        public boolean equals(Object obj) {
            if(obj==this)   return true;
            if(!(obj instanceof PhoneNumber))
                return false;
            PhoneNumber pn=(PhoneNumber)obj;
            return pn.areaCode ==areaCode
                    &&pn.prefix == prefix
                    &&pn.lineNumber == lineNumber;
        }
        /*这里将自己实现的hashCode注释,测试的时候是没有这个方法的
        @Override //正确的hashCode()方法重写
        public int hashCode() {
            int result=17;
            result =31*result + areaCode;
            result =31*result + prefix;
            result =31*result + lineNumber;
            return result;
        }
        */
    }

    测试代码如下:

    import hashCode.PhoneNumber;
    //如果你不明白哈希表的工作原理,请自行百度
    public class HashCodeMain {
        public static void main(String[] args) {
            Map<PhoneNumber, String> map=new HashMap<PhoneNumber, String>();
            PhoneNumber pn=new PhoneNumber(707, 867, 5309);
            map.put(pn, "Jenny");
            map.get(new PhoneNumber(707, 867, 5309));//null
            map.get(pn);//Jenny
    
        }   
    }

    上面的结果违反了第二条约定:相等的对象必须具有相等的散列值;此处的相等是根据equals()方法的逻辑相等,而默认的散列值却被实现成了与对象地址相关。可见equals()方法的逻辑相等与hashCode()方法的地址相等发生了冲突。
    如果我们取消hashCode()方法的注释,那么这个测试代码的结果就会变成两个Jenny,而这才是我们想要的结果。

    3.如何正确的覆盖hashCode()?

    • 把某个非零的常数值,比如说17,保存在一个名为result的int类型的变量中
    • 对于对象中每个关键域f(指equals()方法中涉及的每个域),完成以下步骤:

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

    1.如果该域是boolean,则计算(f ? 1:0)。
    2.如果该域是byte,char,short,或int类型,则计算(int)f。
    3.如果该域是long类型,则计算(int)(f^(f>>>32))。
    4.如果该域是float类型,则计算Float.floatToBits(f)。
    5.如果该域是double类型,则计算Double.doubleToLongBits(f),然后按照步骤a.3,为得到的long类型计算散列值。
    6.如果该域是一个对象引用,并且该类的equals()方法通过递归的调用equals()的方式来比较这个域,则同样为这个域递归地调用hashCode();如果这个域的值为null,则返回0。
    7.如果该域是一个数组,则要把每一个元素当做单独的域来处理。可以递归地引用上述规则。如果数组中的每个元素都很重要,可以使用JDK1.5增加的方法Arrays.hashCode()方法。

      `public static int hashCode(long a[]) {
        if (a == null)
            return 0;    
        int result = 1;
        for (long element : a) {
            int elementHash = (int)(element ^ (element >>> 32));
            result = 31 * result + elementHash;
        }
    
        return result;
    }`
    

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

    • 返回result(如果一个类是不可变的,并且计算散列码的开销也比较大,就应该考虑把散列码作为属性存在对象内部,而不是每次请求的时候都重新计算散列码)。

    参考资料:
    《Effective Java》

  • 相关阅读:
    给Lisp程序员的Python简介
    QuickLisp常用命令
    修改windows cmd字体
    Eclipse生成JavaDoc时指定字符集, 避免页面乱码
    css颜色名
    css3动画
    使用placeholder实现动态配置persistence.xml
    spring下配置tomcat jdbc pool 报找不到"com.mysql.jdbc.Driver"类
    去除移动端浏览器点击元素时的边框
    JS经典闭包问题
  • 原文地址:https://www.cnblogs.com/lizijuna/p/11907414.html
Copyright © 2020-2023  润新知