zoukankan      html  css  js  c++  java
  • JAVA正确地自定义比较对象---如何重写equals方法和hashCode方法

    在实际应用中经常会比较两个对象是否相等,比如下面的Address类,它有两个属性:String province 和 String city。

    public class Address {
        private String province;
        private String city;
        public String getProvince() {
            return province;
        }
        public void setProvince(String province) {
            this.province = province;
        }
        public String getCity() {
            return city;
        }
        public void setCity(String city) {
            this.city = city;
        }
        
        public Address() {}
        public Address(String province, String city) {this.province = province; this.city = city;}
        
    }

    在现实生活中我们认为若两个 Address 的 province 和 city 属性相同,则它们应该是同一个地址(省市都一样,当然就是同一个地区啦)。但下面的代码却表明:address1 和 address2 是两个“不同的地址”

     1 public class TestAddress {
     2     
     3     public static void main(String[] args) {
     4         Address address1 = new Address("广东","广州");
     5         Address address2 = new Address("广东", "广州");
     6         
     7         System.out.println(address1 == address2);//false
     8         System.out.println(address1.equals(address2));//false
     9         System.out.println(address1.hashCode() == address2.hashCode());//false
    10     }
    11 }

    其原因是:在JAVA(编程语言)中比较对象 和 现实生活中 比较两个对象是两个不同的概念,前者暂且称为“物理相等”,而后者是“逻辑相等”。

    adress1==adress2 是根据两个对象的内存地址是否相同进行比较的,第4、5行分别 new 了两个对象,这两个对象存储在内存中不同的地址,当然不会相等了。

    由于Address类并没有重写equals方法,那么address1.equals(address2) 执行的就是Object类的equals方法,而java.lang.Object类的equlas方法是这样实现的:

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

    JDK中对该方法的注释如下:也就是说:Object类的equals方法 是通过 == 来比较两个对象是否相等的,也即根据 对象x引用 和 对象y 的引用是否指向内存中的同一个地址 来判断 对象x 和 对象y 是否相等。

         * The {@code equals} method for class {@code Object} implements
         * the most discriminating possible equivalence relation on objects;
         * that is, for any non-null reference values {@code x} and
         * {@code y}, this method returns {@code true} if and only
         * if {@code x} and {@code y} refer to the same object
         * ({@code x == y} has the value {@code true}).

    而按照现实思维,既然 adress1 和 address2 都代表广东广州,那么在程序中它们两个对象比较就应该相等(逻辑上相等),也即address1.equals(address2)应该返回true才对。于是就需要覆盖 Object 类中的 equals 方法 和 hashCode方法了。

    而覆盖 equals方法 和hashCode方法是需要技巧的。

    ①覆盖了Object类的equals方法后,需要再覆盖 Object类的hashCode方法。为什么呢?----不要违反“hashCode方法的 通过约定(general contract) ”

    Object类的equals方法上的注释如下:

       / * Note that it is generally necessary to override the {@code hashCode}
         * method whenever this method is overridden, so as to maintain the
         * general contract for the {@code hashCode} method, which states
         * that equal objects must have equal hash codes.
        */
        public boolean equals(Object obj) {
            return (this == obj);
        }

    而这个“通用约定”就是:❶若两个对象根据equals(Object)方法比较相等,那么调用这两个对象中任意一个对象的hashCode方法 都必须 产生同样的整数结果。

    ❷若两个对象根据equals(Object)方法比较相等,那么调用这两个对象中任意一个对象的hashCode方法 可以 产生相同的整数结果,但是最好 产生不同的整数结果,这样可以提供散列表的性能(当要把Address类 作为 键 put 到HashMap中时,可以减少冲突,可参考这篇文章

    那具体如何正确地覆盖equals()呢?《Effective JAVA》里面给出了方法,套路是一样的,其目标是保证:自反性、对称性、一致性。总之,对于上面的Address类而言,可以这样:

    1     @Override
    2     public boolean equals(Object obj) {
    3         if(obj == this)
    4             return true;
    5         if(!(obj instanceof Address))
    6             return false;
    7         Address address = (Address)obj;
    8         return address.getProvince().equals(province) && address.getCity().equals(city);
    9     }

    第8行从表明如果两个Address的 province 和 city 相同,那这两个Address就是相同的,这样equals方法就会返回true了。(不考虑字符串大小写问题)

    覆盖完了equals(),接下来就是 覆盖hashCode()了。覆盖hashCode()的目标是:

    如果两个对象 address1.equals(address2) 返回 false,那么 address1.hashCode() 最好 不等于 address2.hashCode()

    当然,没有超级完美的hashCode(),如果相等了,那么当 hashMap.put(address1,value1)  hashMap.put(address2,value2) 就会put到同一个 hashmap的同一个槽下了。

    重写了equals和hashCode的Address类如下:

    public class Address {
        private String province;
        private String city;
        public String getProvince() {
            return province;
        }
        public void setProvince(String province) {
            this.province = province;
        }
        public String getCity() {
            return city;
        }
        public void setCity(String city) {
            this.city = city;
        }
        
        public Address() {}
        public Address(String province, String city) {this.province = province; this.city = city;}
        
        @Override
        public boolean equals(Object obj) {
            if(obj == this)
                return true;
            if(!(obj instanceof Address))
                return false;
            Address address = (Address)obj;
            return address.getProvince().equals(province) && address.getCity().equals(city);
        }
        
        @Override
        public int hashCode() {
            int result = 17;
            result += 31 * province.hashCode();
            result += 31 * city.hashCode();
            return result;
        }
    }

    测试类如下:

    import java.util.HashMap;
    import java.util.Map;
    
    public class TestAddress {
        
        public static void main(String[] args) {
            Address address1 = new Address("广东","广州");
            Address address2 = new Address("广东", "广州");
            
            System.out.println(address1 == address2);//false
            System.out.println(address1.equals(address2));//true
            System.out.println(address1.hashCode() == address2.hashCode());//true
            
            Address diff1 = new Address("四川","成都");
            Address diff2 = new Address("四川","绵阳");
            System.out.println(diff1 == diff2);//false
            System.out.println(diff1.equals(diff2));//false
            System.out.println(diff1.hashCode() == diff2.hashCode());//false
            
            Map<Address, Integer> hashMap = new HashMap<Address, Integer>();
            hashMap.put(address1, 1);
            hashMap.put(address2, 2);//address2的hashCode 和 address1 相同,因此 put 方法会覆盖 address1 对应的 Value值1
            System.out.println(hashMap.get(address1));//2
            System.out.println(hashMap.get(address2));//2
            
            hashMap.put(diff1, 1);
            hashMap.put(diff2, 2);
            System.out.println(hashMap.get(diff1));//1
            System.out.println(hashMap.get(diff2));//2
        }
    }

    最后,其实Eclipse里面为我们提供了自动 生成 equals和hashCode的方法,可参考:JAVA中equals方法与hashCode方法学习。自动生成的方法如下:(还是自动生成的更专业呀。。。。)

        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + ((city == null) ? 0 : city.hashCode());
            result = prime * result + ((province == null) ? 0 : province.hashCode());
            return result;
        }
        @Override
        public boolean equals(Object obj) {
            if (this == obj)
                return true;
            if (obj == null)
                return false;
            if (getClass() != obj.getClass())
                return false;
            Address other = (Address) obj;
            if (city == null) {
                if (other.city != null)
                    return false;
            } else if (!city.equals(other.city))
                return false;
            if (province == null) {
                if (other.province != null)
                    return false;
            } else if (!province.equals(other.province))
                return false;
            return true;
        }

    参考:《effective java》

    http://www.cnblogs.com/hapjin/p/4582795.html

    原文:http://www.cnblogs.com/hapjin/p/7327839.html

  • 相关阅读:
    大数据经典学习路线(及供参考)
    Redis配置规范
    mysql中datetime和timestamp类型的区别
    bigint(10)和bigint(20)的区别
    Redis分布式锁,基于StringRedisTemplate和基于Lettuce实现setNx
    Spring security UserDetailsService autowired注入失败错误
    Java Print 打印
    Spring boot Junit Test单元测试
    RESTful作用与特性
    JS匿名函数理解
  • 原文地址:https://www.cnblogs.com/hapjin/p/7327839.html
Copyright © 2011-2022 走看看