覆盖equals时总要覆盖hashCode

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

举个例子:web

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)));

    }

}

运行结果:ide

null

咱们设想的结果是 Sean,可是由于覆盖了equals方法而没有覆盖hashCode方法,就出现了异常,如今先用一个及其简单的办法覆盖一下hashCode方法看看能不能获得正常结果:svg

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集合的工做原理提及了:this

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

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

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

public native int hashCode();

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

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方法会构成不一样的散列方式,有好有坏 。