热爱技术,追求卓越
不断求索,精益求精

秒懂java,覆盖equals时没有覆盖hashCode后果是多么的惨烈!

在前一篇文章《秒懂java,你真的会写equals方法吗?》中提到“覆盖equals时总要覆盖hashCode方法,避免一些预想不到的错误”。我们还是使用上篇文章的例子,修改一下User类后:

package cn.lovecto.test;

import java.util.HashMap;

public class User {
    /** 用户名 */
    private String name;
    /** 手机号 */
    private String phone;
    public User(String name, String phone) {
        super();
        this.name = name;
        this.phone = phone;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getPhone() {
        return phone;
    }
    public void setPhone(String phone) {
        this.phone = phone;
    }
    @Override
    public boolean equals(Object obj) {
        //1.使用“==”操作符检查参数是否是这个对象的引用
        if (this == obj){
            return true;
        }
        //2.检查参数是否为空
        if (obj == null){
            return false;
        }
        //3.检查参数类型是否匹配
        if (getClass() != obj.getClass())
            return false;
        //4.把参数转换为正确的类型
        User other = (User) obj;
        //5.对于类中需要进行逻辑判断的属性,检查参数的属性是否与这个对象的属性相同
        if (name == null) {
            if (other.name != null)
                return false;
        } else if (!name.equals(other.name))
            return false;
        if (phone == null) {
            if (other.phone != null)
                return false;
        } else if (!phone.equals(other.phone))
            return false;
        return true;
    }
    public static void main(String[] args) {
        HashMap<User, User> userMap = new HashMap<User, User>();
        userMap.put(new User("a", "135xxxxxxxx"), new User("a", "135xxxxxxxx"));
        User u = userMap.get(new User("a", "135xxxxxxxx"));
        System.out.println(u.getName() + "\t" + u.getPhone());
    }
}

运行结果如下:

Exception in thread "main" java.lang.NullPointerException
    at cn.lovecto.test.User.main(User.java:73)

User类中并没有重写hashCode方法,在main函数中,put的key和get的key调用equals肯定是相等的,但却存入HashMap后却找不到存入的对象了。出现这个的结果就是由于在重写
equals方法的同时没有重写hashCode方法导致的。

hashCode的通用约定

  1. 只要程序不挂,不重启,那么在程序执行期间,如果对象的equals方法的比较操作用到的所有信息均没有被修改,那么对同一个对象不管调用多少次,hashCode方法都必须返回同一个整数。同一个程序的多次执行,每次执行返回的整数可以不一样。
  2. 如果两个对象使用equals比较是相等的,那么调用这两个对象中的任意一个对象的hashCode方法都必须产生同样的整数结果。
  3. 如果两个对象使用equals比较是不相等的,那么调用这两个对象中任意一个对象的hashCode方法,则不一定要产生不同的整数结果(可以相同,最好不同)。给不相等的对象调用hashCode方法产生的整数结果不同,对提高散列表(hash table)的性能是有积极作用的。

重写hashCode的常规步骤

一个好的hashCode方法通常以“为不相等的对象产生不相等的hash值”为目标。理想情况下,hashCode方法应该把集合中的不相等的对象均匀地分布到所有可能的hash值上。一种常规的做法是:

  1. 把某个非零的常数值,比如1,保存在一个名为result的int类型变量中。
  2. 对于对象中每个关键的属性a(equals方法中涉及到的属性),完成下面的步骤:
    (1)为该属性a计算int类型的hash值c:
    如果a是boolean类型,则”c = a ? 1 : 0;”。
    如果a是byte、char、short、int类型,则”c = (int)a;”。
    如果a是long类型,则”c=(int)(a^(a>>>32));”。
    如果a是float类型,则”c=Float.floatToIntBits(a);”。
    如果a是double类型,则计算”c=(int)(Double.doubleToLongBits(a)^(Double.doubleToLongBits(a)>>>32));”。
    如果a是一个对象引用,则使用该对象引用的hashCoder值作为c的值。
    如果a是一个数组,这要把没一个元素作为单独的属性来处理,按照上面提到的规则计算hash值,再把这些值组合起来(组合方式参考(2))。
    (2)按照如下的公式,把c的值合并到result中:
    result = 31 * result + c;
  3. 返回result。
  4. 写单元测试验证一下。

重写hashCode后,运行正常

我们在上面的实例中加入hashCode方法:

@Override
public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + ((name == null) ? 0 : name.hashCode());
    result = prime * result + ((phone == null) ? 0 : phone.hashCode());
    return result;
}

再运行结果如下:

a   135xxxxxxxx

所以重写equals方法的同时切记要重写hashCode方法,不然后果将是非常的惨烈,得到非预想的结果。

赞(0)
未经允许不得转载:LoveCTO » 秒懂java,覆盖equals时没有覆盖hashCode后果是多么的惨烈!

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

热爱技术 追求卓越 精益求精