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也是相同的。

浙公网安备 33010602011771号