effective java学习之对于所有对象都通用的方法——覆盖equals时总是要覆盖hashCode

Object有哪些公用方法?

  答:Clone,getClas,toString,finalize,equals,hashCode,wai,notify,notifyAll方法。

1.什么时候需要覆盖hashCode?

  答:复写 equals 的类也必须复写hashcode,否则无法结合基于散列的集合一起正常工作.。即:hashCode主要用于放在HashMap,HashSet,HashTable中时,计算hash值。

 2.如果覆盖equals时而不覆盖hashCode,会如何?

  见代码:

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;
    }
    @Override
    public boolean equals(Object obj) {
        // 使用==操作符检查参数是否为这个对象的引用
        if (obj == this) {
            return true;
        }
        // 2、使用instanceof操作符检查参数是否为正确的类型
        if (!(obj instanceof PhoneNumber)) {
            return false;
        }
        // 3、把参数转换成正确的类型
        PhoneNumber pn = (PhoneNumber)obj;
        // 4、应该问自己三个问题,它是否是对称的、传递的、一致的。
        return pn.lineNumber == lineNumber && pn.prefix == prefix && pn.areaCode == areaCode;
    }
}

测试结果:

public class Test {
    public static void main(String[] args) {
        Map<PhoneNumber, String> map = new HashMap<PhoneNumber, String>();
        PhoneNumber phoneNumber = new PhoneNumber(707, 867, 9876);  
        map.put(phoneNumber, "Jenny");

        System.out.println(map.get(new PhoneNumber(707, 867, 9876)));  
        System.out.println(map.get(phoneNumber));
        /**
         * null
           Jenny
         */
    }
}

由测试结果可以看出:
  可以发现上面取到的值为null,而不是”Jenny”。

  原因就是没有重写hashCode方法,每次hashCode返回的都是不同的值。

通用约定

  1.只要对象equals所用到的信息没有被修改,对于同一对象多次调用hashcode必须返回同一个整数;

  2.如果2个对象的equals比较相等,则两个对象的hashcode产生同样的整数结果;

  3.如果equals不相等,则两个对象的hashcode不一定产生不同的整数结果。

建议与须知

  1、如果hashcode返回一个常量,则每个对象具有相同的散列吗,,每个对象被映射到同一个散列桶中,散列桶会退化为散列表结构;

  2、必须排除 equals 计算中没有用到的任何域;

  3、不要试图从 散列码计算中,除掉一个对象的关键部分来提高性能;

  4、31 有个很好的特性,即31*i == (i<<5)-i,现代的VM可以自动完成这种优化;

  5、如果一个类是不可变的,且计算hashcode开销比较大,建议将散列表缓存在对象内部。

  而如果我们覆盖了hashCode方法,这时,如果通过hashCode计算出来的值是相等的,就会放在同一个盒子里。这样,只要我们对象中保存的值是完全一致的,就会找到这个key所对应的value。但是hashCode有点类似于分类,这样在数据量比较大的情况下就会大大提高效率。

 方式一:

public class PhoneNumber2 {
    private final int areaCode;
    private final int prefix;
    private final int lineNumber;

    public PhoneNumber2(int areaCode, int prefix, int lineNumber) {
        this.areaCode = areaCode;
        this.prefix = prefix;
        this.lineNumber = lineNumber;
    }
    @Override
    public boolean equals(Object obj) {
        // 使用==操作符检查参数是否为这个对象的引用
        if (obj == this) {
            return true;
        }
        // 2、使用instanceof操作符检查参数是否为正确的类型
        if (!(obj instanceof PhoneNumber2)) {
            return false;
        }
        // 3、把参数转换成正确的类型
        PhoneNumber2 pn = (PhoneNumber2)obj;
        // 4、应该问自己三个问题,它是否是对称的、传递的、一致的。
        return pn.lineNumber == lineNumber && pn.prefix == prefix && pn.areaCode == areaCode;
    }
    @Override
    public int hashCode() {
//        return super.hashCode();
        return 42;
    }
}

测试代码:

public class Test {
    public static void main(String[] args) {
        Map<PhoneNumber2, String> map2 = new HashMap<PhoneNumber2, String>();
        PhoneNumber2 phoneNumber2 = new PhoneNumber2(707, 867, 9876);  
        map2.put(phoneNumber2, "lowi");

        System.out.println(map2.get(new PhoneNumber2(707, 867, 9876)));  
        System.out.println(map2.get(phoneNumber2));
System.out.println(map2.get(new PhoneNumber2(707, 867, 2211)));
/** * lowi * lowi
* null
*/ } }

方式二:
  替换代码中的hashCode():

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

  测试代码与结果一样。这里省略
  说明:这样得到的hashCode值跟里面的属性内容是有关系的,如果属性内容都相同,hashCode也就相同了。

  首先两种方法都可以。通过上面的分析,从效率的角度来考虑,当然是第二种方法更为恰当。

内容扩展:

  分析HashMap的源码:

@Override
public V put(K key, V value) {
    // 只展示关键代码
    // 插入的位置索引值会对key取hansh
    int hash = Collections.secondaryHash(key);

}
public static int secondaryHash(Object key) {
    return secondaryHash(key.hashCode());
}

     说明:从上面可以看到传入的是key的hashCode,所以即使是相同的key,如果它的hashCode不同对应的index索引也不同。

下面代码:

Map<String, String> map = new HashMap<String, String>();
map.put(new String("aaa"),  "hello");
map.get(new String("aaa"));

  返回的就是hello,所以我们可以来看看String里面是怎么实现hashCode的。

public int hashCode() {
    int h = hash;//hash默认为0,用来缓存String里面的hashCode
    if (h == 0 && value.length > 0) {
        char val[] = value;
        for (int i = 0; i < value.length; i++) {
            h = 31 * h + val[i];
        }
        hash = h;
    }
    return h;
}

  根据字符串的内容来确定hashCode的,所以如果字符串的内容是相同的,那么hashCode也是相同的。

posted @ 2018-04-22 20:26  小码农成长记  阅读(183)  评论(0)    收藏  举报