覆盖equals时总要覆盖hashCode

2017-03-20  本文已影响235人  大海孤了岛
1. 什么是hashcode方法?
  • hashcode方法返回对象的哈希码值
2. hashcode相等与对象相等之间的关系:(保证设计是规范的前提下)
  • 如果两个对象相同,那么两个对象的hashcode也必须相同
3. 为什么覆盖equals方法时总要覆盖hashcode方法?

因为如果不覆盖equals方法的话,相等的对象可能返回的不相同的hash code。

例子:我们创建两个相同值的对象,然后将p1对象作为key,"jenny"插入到hashmap中,再将p2对象作为key,获取对应的value值

public 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;
    }
    //覆盖equals方法
    @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;
    }

    public static void main(String[] args){
        Map<PhoneNumber, String> m = new HashMap<>();
        //创建两个相同的对象
        PhoneNumber p1 = new PhoneNumber(707, 867, 5309);
        PhoneNumber p2 = new PhoneNumber(707, 867, 5309);
        //添加到hashmap中
        m.put(p1, "Jenny");
        //比较对象p1和p2
        System.out.println("p1.equals(p2): " + p1.equals(p2));
        System.out.println("p2.equals(p1): " + p2.equals(p1));
        //从hashmap中去获取对象p1和p2
        System.out.println("get p1 from hashmap: " + m.get(p1));
        System.out.println("get p2 from hashmap: " + m.get(p2));
    }
}
输出结果:
p1.equals(p2): true
p2.equals(p1): true
get p1 from hashmap: Jenny
get p2 from hashmap: null

前两个结果说明p1和p2为同一对象,但我们发现以p1作为key时,是可以获取到对应的value,而以p2作为key时,却获取到null。

实际上,是因为PhoneNumber类没有覆盖hashcode方法,从而导致两个相等的实例具有不相等的散列码。

//输出p1和p2的hashcode
System.out.println("p1's hashcode: " + p1.hashCode());
System.out.println("p2's hashcode: " + p2.hashCode());
输出结果:
p1's hashcode: 460141958
p2's hashcode: 1163157884

因此,导致put方法时把电话存放在一个散列桶中,而get方法却在另个散列桶中查找这个电话号码。即使这两个实例正好放到同一个散列桶中(发生“冲突”的情况),get方法也必定会返回null,因为hashmap有一项优化,可以将与每个相关联的散列码缓存起来,如果散列码不匹配,那么将会返回null。

4. 如何在覆盖equals方法时覆盖hashcode方法?

实际上,问题很简单,只要我们重写hashcode方法,返回一个适当的hash code即可。

@Override
public int hashCode() {
    return 42;
}

这样的确能解决上面的问题,但实际上,这么做,会导致很差的性能,因为它总是确保每个对象都具有同样的散列码。因此,每个对象都被映射到同一个散列桶中,使得散列表退化成链表。

** 一个好的散列函数通常倾向于“为不相等的对象产生不同的散列码”**。
理想情况下,散列函数应该把集合中不相等的实例均匀地分布到所有可能的散列值上。但实际上,要达到这种理想的情形是非常困难的。

5. 如何设置一个好的散列函数?

a. 为对象计算int类型的散列码c:

b. 将获取到的c合并:result = 31 * reuslt + c;

c. 返回result

比如,我们可以优化上面的hashcode方法:

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

如果一个类是不可变类,并且计算散列码的开销也比较大,就应该考虑吧散列码缓存在对象内部,而不是每次请求的时候都重新计算散列码。

 private volatile static int hashcode;
    
    @Override
    public int hashCode() {
        int result = hashcode;
        if (result == 0){
            result = 31 * result + areaCode;
            result = 31 * result + prefix;
            result = 31 * result + lineNumber;
            hashcode = result;
        }
        return result;
    }
上一篇下一篇

猜你喜欢

热点阅读