覆盖equals时总要覆盖hashCode

当类覆盖equals但未覆盖hashCode时,会导致基于散列的集合如HashMap、HashSet等无法正常工作。覆盖hashCode的基本原理是通过哈希函数快速定位元素。不正确的hashCode实现可能导致效率低下。正确覆盖hashCode时,其涉及的参数应与equals方法一致,以确保一致性。合理的hashCode设计对集合性能至关重要。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

如何某类覆盖了equals方法而没有覆盖hashCode方法,则该类无法和基于散列的集合正常运作,比如HashMap、HashSet、HashTable集合。

举个例子:

public class Main {

        static 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) { //覆盖equals方法
            if(obj.getClass()!=getClass())
            {
                return false;
            }
            PhoneNumber phoneNumber=(PhoneNumber)obj;
            return phoneNumber.areaCode==areaCode&&phoneNumber.prefix==prefix&&phoneNumber.lineNumber==lineNumber;
        }
    }

    public static void main(String[] args) {
       Map<PhoneNumber,String> phoneNumberMap=new HashMap<>();
       phoneNumberMap.put(new PhoneNumber(100,200,300),"Sean");
       System.out.println(phoneNumberMap.get(new PhoneNumber(100,200,300)));

    }

}

运行结果:

null

我们设想的结果是 Sean,但是因为覆盖了equals方法而没有覆盖hashCode方法,就出现了异常,现在先用一个及其简单的办法覆盖一下hashCode方法看看能不能得到正常结果:

public class Main {

        static 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.getClass()!=getClass())
            {
                return false;
            }
            PhoneNumber phoneNumber=(PhoneNumber)obj;
            return phoneNumber.areaCode==areaCode&&phoneNumber.prefix==prefix&&phoneNumber.lineNumber==lineNumber;
        }

            @Override
            public int hashCode() { //一个极其简单的覆盖hashCode方法
                return 23;
            }
        }

    public static void main(String[] args) {
       Map<PhoneNumber,String> phoneNumberMap=new HashMap<>();
       phoneNumberMap.put(new PhoneNumber(100,200,300),"Sean");
       System.out.println(phoneNumberMap.get(new PhoneNumber(100,200,300)));

    }

}

运行结果:

Sean

这就得到了我们预期的结果了,为何hashCode被如此覆盖就能正常工作呢?这就要从这些个Hash集合的工作原理说起了:

这些Hash集合,在添加,查找的时候都需要先看下集合里有没有该元素,如何查找呢?如果一个个用equals比较那效率就太低了,也不是Hash集合的作风,既然是Hash集合自然要用哈希函数对每个元素用hashCode方法生成一个哈希值,根据哈希值来存储到特定的存储空间,如果产生重复哈希值就可以利用拉链法这样的办法解决,这样所有元素散列存储在内存空间里。添加的过程就为先用hashCode方法对元素产生哈希值,然后根据这个哈希值找到存储空间,对该段存储空间的元素再用equals进行比对,如果有相同的元素则不添加,没有则添加。查找过程类似。这样的哈希过程就大大提高了效率。

根据这个原理,所以刚才的极简的hashCode覆盖方法就等于把所以元素的哈希值都定位同一个常数,把所有元素线性存储在这个空间里,虽然这个办法可以暂时解决问题但是它是十分恶劣的办法,当元素数量大大增加时效率会极其低。

那么为什么原来hashCode办法会无效呢?看看这个原来的hashCode方法:

public native int hashCode();

这个就是原来继承Object的hashCode方法,这个方法是一个外部方法,功能是返回元素在内存空间中的地址,则这个hashCode方法刚好配合Object的equals方法使用:

   public boolean equals(Object obj) {
        return (this == obj);
    }

Object的equals方法正好是比较元素地址的,这与hashCode返回元素地址是一致的。所以这告诉我们覆盖hashCode方法的时候里面涉及的参数要和覆盖equals方法里面依赖的参数一致,尽量包含到元素类所有特征性变量。根据这个准则我们再修改一下例子中的hashCode方法:

public class Main {

        static 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.getClass()!=getClass())
            {
                return false;
            }
            PhoneNumber phoneNumber=(PhoneNumber)obj;
            return phoneNumber.areaCode==areaCode&&phoneNumber.prefix==prefix&&phoneNumber.lineNumber==lineNumber;
        }

            @Override
            public int hashCode() { //改良版的hashCode覆盖方法
               int result;
               result=areaCode*100+prefix*10+lineNumber;
               return result;
            }
        }

    public static void main(String[] args) {
       Map<PhoneNumber,String> phoneNumberMap=new HashMap<>();
       phoneNumberMap.put(new PhoneNumber(100,200,300),"Sean");
       System.out.println(phoneNumberMap.get(new PhoneNumber(100,200,300)));
    }

}

运行结果:

Sean

结果没有问题,但是这个hashCode方法可能也不是最好的,设计这个方法是一门学问,不同的hashCode方法会构成不同的散列方式,有好有坏 。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值