Java equals方法与hashCode

1. Object equals方法与hashCode

Java Object类中equals方法为:

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

所以如果没有重写Object类的equals方法,那么equals与==计算结果是相同的。

Object类的hashCode方法是一个native方法,看不到具体实现,hashCode返回的是由对象存储地址转化得到的值

public native int hashCode();

2. 重写equals方法

首先需要说明的是重写equals方法看似很简单,但是可能会产生非常多的问题,大多数情况下我们并不需要重写equals方法,如果实在需要重写equals方法,推荐采用Google开源的Auto Value 框架,它会自动生成equals和hashCode方法,或者IDE也有工具生成这些方法(更不推荐这个方法),但是都比自己重写会少很多问题

重写的equals方法需要满足五个性质

  1. 自反性:对象必须等于其自身

  2. 非空性:所有对象都不能等于null

  3. 对称性:如果a.equals(b)为true,那么b.equals(a)必须也为true

    重写equals方法是很有可能出现这个问题的,例如你写了一个类CaseInsensitiveString,重写equals要 求字符串不区分大小写,那么a是CaseInsensitiveString类型,值为"Pow",b为String类型,值为“pow”,那么a.equals(b)为true,而b.equals(a)调用的是String的equals方法,区分大小写,则为false,违反了对称性。
    如何处理这种情况呢,可以禁止与String互操作,代码如下

@Oveerride
public boolean equals(Object o){
	return o instanceof CaseInsensitiveString && ...
}

对象o 如果是String类型,那么o instanceof CaseInsensitiveString则为false,对称性得到保证。同时还保证了非空性。

还有个问题需要思考,例如一个类A有属性x,y,A重写了equals方法,类B继承了类A,同时增加了z属性,但是沿用了A的equals方法,那么B的实例k和m在做equals比较的时候z属性会被直接忽略掉,虽然不违反equals约定,但是明显这种比较是不正确的。

如果我们重写子类的equals方法呢,那么A类实例a与B类实例b,a.equals(b)的结果为true,因为z属性将不会拿来比较,会被忽略,b.equals(a)结果为false,因为z属性会被拿来比较,但是a中没有z属性,这又违反了对称性。处理这个问题有一个权宜之计,就是“复合优先于继承”,不采用继承的方法。当然,如果继承的是抽象类的话,因为抽象类无法实例化对象,也就不会产生这些问题。

  1. 传递性:如果a.equals(b)为true,b.equals©为true,则a.equals©也为true

  2. 一致性:相等的对象永远相等,不相等的对象永远不相等

对了,还要考虑一个问题,不要将equals声明重的Object对象替换成其它类型。

public boolean equals(MyClass o)

如果你写成这样,就是重载,而不是覆盖,可能到最后你会花上数小时都不知道为什么得不到自己想要的结果。重写的时候带上注解@Override可以避免这种问题

重写equals方法必须重写hashCode方法

看很多文章都提到了这一点,但是只有一句因为equals相等时hashCode必须相等,所以要重写equals方法,第一次看的时候特别懵,琢磨了挺久才明白是怎么一回事。

首先要明白HashCode与equals的关系

  • 应用程序执行期间,对象的equals方法的比较操作所用到的信息没有修改,则同一个对象的hashCode方法都必须返回同一个值。
  • 如果equals结果为true,则两个对象的hashCode必须相等(重点)
  • 如果equals结果为false,则hashCode值不一定不同,例如“重”,“地”的hash值就相同,这个现象叫做哈希碰撞

所以,map在查找key的时候,先用哈希函数找到相同的key(hashCode相同值不一定相同),然后再用equals判断是否相同。如果hash值都不相同,那么就没必要进行equals操作了,大大提高了效率。

所以当你想要重写equals方法时,一定要想到我重写后的equals方法为true的两个对象,如果我不重写hashCode方法,会不会导致两个对象的hash值不同,违反了上文的重点要求

可以自己写个例子试验一下,创建一个Mychar类,有个char类型数组chars,重写了equals方法但是没用重写hashCode方法(随便写的,只是为了说明问题,可能没有保证五条性质)

@Data
@AllArgsConstructor
public class MyChar {
    private char[] chars;

    @Override
    public boolean equals(Object obj) {
        if(!(obj instanceof MyChar)){
            return false;
        }else{
            MyChar anotherObj = (MyChar)obj;
            for (int i = 0; i < chars.length; i++) {
                if (this.chars[i] != anotherObj.chars[i]){
                    return false;
                }
            }
            return true;
        }
    }
}

编写测试类Test,测试以下项目

public class Test {
    public static void main(String[] args) {

        char[] Char = new char[]{'a','b'};
        MyChar myChar1 = new MyChar(Char);
        MyChar myChar2 = new MyChar(Char);

        System.out.println(myChar1 == myChar2);  //是否时同一对象
        System.out.println(myChar1.equals(myChar2));  //逻辑上是否相等
        System.out.println(myChar1.hashCode());    // hash值
        System.out.println(myChar2.hashCode());

    }
}
false
true
1878246837
929338653

进程已结束,退出代码为 0

可以看到两个不是同一对象equals的结果为true,但是hash值完全不相同,这就是不重写hashCode方法的后果。

我们都知道String类重写了equals和hashCode方法,可以学习一下它的方法,具体代码如下:

public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        if (anObject instanceof String) {
            String anotherString = (String)anObject;
            int n = value.length;
            if (n == anotherString.value.length) {
                char v1[] = value;
                char v2[] = anotherString.value;
                int i = 0;
                while (n-- != 0) {
                    if (v1[i] != v2[i])
                        return false;
                    i++;
                }
                return true;
            }
        }
        return false;
    }
public int hashCode() {
        int h = hash;
        if (h == 0 && value.length > 0) {
            char val[] = value;

            for (int i = 0; i < value.length; i++) {
                h = 31 * h + val[i];
            }
            hash = h;
        }
        return h;
    }

3. 结语

总之尽量不要自己重写equals方法,除非迫不得已,如果要重写推荐采用Google的开源框架Auto Value实现,通常优于手工实现它们。

4. 参考文献

本文主要参考《Effective Java》第三版,其它参考资料略。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值