重写equals()和hashcode()方法详解

本文将通过一个示例程序来深入讲解java的equals()方法
1、示例程序:
package cn.galc.test;

public class TestEquals {
	public static void main(String[] args) {
      /** 
        * 这里使用构造方法Cat()在堆内存里面new出了两只猫,
        * 这两只猫的color,weight,height都是一样的,
        * 但c1和c2却永远不会相等,这是因为c1和c2分别为堆内存里面两只猫的引用对象,
        * 里面装着可以找到这两只猫的地址,但由于两只猫在堆内存里面存储在两个不同的空间里面,
        * 所以c1和c2分别装着不同的地址,因此c1和c2永远不会相等。
        */
       Cat c1 = new Cat(1, 1, 1);
       Cat c2 = new Cat(1, 1, 1);
       System.out.println("c1==c2的结果是:"+(c1==c2));//false
       System.out.println("c1.equals(c2)的结果是:"+c1.equals(c2));//false
    }
 }
 
 class Cat {
     int color, weight, height;
 
     public Cat(int color, int weight, int height) {
         this.color = color;
         this.weight = weight;
         this.height = height;
     }
 }
2、画出内存分析图分析c1和c2比较的结果

程序:
Cat c1 = new Cat(1,1,1);
Cat c2 = new Cat(1,1,1);
执行完之后内存之中的布局如下图所示,
在这里插入图片描述

c1指向一个对象,c2也指向一个对象,c1和c2里面装着的是这两只Cat对象在堆内存里面存储的地址,由于这两只Cat对象分别位于不同的存储空间,因此c1和c2里面装着的地址肯定不相等,因此c1和c2这两个引用对象也肯定不相等。因此执行:“System.out.println(c1==c2);”打印出来的结果肯定是false。因此你new出来了两个对象,你放心,这两个对象的引用永远不一样,一样的话就会把其中一个给覆盖掉了,这个可不成。c1是不是等于c2比较的是c1和c2这两个引用里面装着的内容,因为new出来的两个对象的它们的引用永远不一样,因此c1和c2这两个引用的内容也永远不一样,因此c1永远不可能等于c2。因此通过比较两个对象的引用是永远无法使得两个对象相等的,一模一样的。

要想判断两个对象是否相等,不能通过比较两个对象的引用是否相等,这是永远都得不到相等的结果的,因为两个对象的引用永远不会相等,所以正确的比较方法是直接比较这两个对象,比较这两个对象的实质是不是一样的,即这两个对象里面的内容是不是相同的,通过比较这两个对象的属性值是否相同而决定这两个对象是否相等。

Object类提供了一个equals()方法来比较两个对象的内容是否相同,因此我们可以采用这个方法去比较两个对象是否在逻辑上“相等”。如:c1.equals(c2);这里是调用从Object类继承下来的equals()方法,通过查阅API文档得到Object类里的equals方法的定义如下:

public boolean equals(Object obj)

3、在Cat类中重写equals()方法
class Cat {
     int color, weight, height;
 
     public Cat(int color, int weight, int height) {
         this.color = color;
         this.weight = weight;
         this.height = height;
     }
     
     /**
      * 这里是重写相等从Object类继承下来的equals()方法,改变这个方法默认的实现,
      * 通过我们自己定义的实现来判断决定两个对象在逻辑上是否相等。
      * 这里我们定义如果两只猫的color,weight,height都相同,
      * 那么我们就认为这两只猫在逻辑上是一模一样的,即这两只猫是“相等”的。
      */
     public boolean equals(Object obj){
         if (obj==null){
             return false;
         }
         else{
             /**
              * instanceof是对象运算符。
              * 对象运算符用来测定一个对象是否属于某个指定类或指定的子类的实例。
              * 对象运算符是一个组合单词instanceof。
              * 该运算符是一个双目运算符,其左边的表达式是一个对象,右边的表达式是一个类,
              * 如果左边的对象是右边的类创建的对象,则运算结果为true,否则为false。
              */
             if (obj instanceof Cat){
                 Cat c = (Cat)obj;
                 if (c.color==this.color && c.weight==this.weight && c.height==this.height){
                     return true;
                 }
             }
         }
         return false;
     }
 }

重写equals()方法的要求,必须实现等价关系

  • 自反性:对于任何非空引用x,x.equals(x)应该返回true;
  • 对称性:对于任何引用x和y,如果x.equals(y)返回true,那么y.equals(x)也应该返回true;
  • 传递性:对于任何引用x、y和z,如果x.equals(y)返回true,y.equals(z)返回true,那么x.equals(z)也应该返回true;
  • 一致性:如果x和y引用的对象没有发生变化,那么反复调用x.equals(y)应该返回同样的结果;
  • 非空性:对于任意非空引用x,x.equals(null)应该返回false;
4、重写equals()方法也必须重写hashcode()方法
原因:

主要原因是默认从Object继承来的hashCode是基于对象的ID实现的。
如果你重写了equals,比如说是基于对象的内容实现的,而保留hashCode的实现不变,那么很可能某两个对象明明是“相等”,而hashCode却不一样。
所以如果我们对equals方法进行了重写,建议一定要对hashCode方法重写,以保证相同的对象返回相同的hash值,不同的对象返回不同的hash值。

怎么重写hashcode()呢?

Google首席Java架构师Joshua Bloch在他的著作《Effective Java》中提出了一种简单通用的hashCode算法

  1. 初始化一个整形变量,为此变量赋予一个非零的常数值,比如int result = 17;
  2. 选取equals方法中用于比较的所有域,然后针对每个域的属性进行计算:
    (1) 如果是boolean值,则计算f ? 1:0
    (2) 如果是byte\char\short\int,则计算(int)f
    (3) 如果是long值,则计算(int)(f ^ (f >>> 32))
    (4) 如果是float值,则计算Float.floatToIntBits(f)
    (5) 如果是double值,则计算Double.doubleToLongBits(f),然后返回 的结果是long,再用规则(3)去处理long,得到int
    (6) 如果是对象应用,如果equals方法中采取递归调用的比较方式,那么hashCode中同样采取递归调用hashCode的方式。  否则需要为这个域计算一个范式,比如当这个域的值为null的时候,那么hashCode 值为0
    (7) 如果是数组,那么需要为每个元素当做单独的域来处理。如果你使用的是1.5及以上版本的JDK,那么没必要自己去重新遍历一遍数组,java.util.Arrays.hashCode方法包含了8种基本类型数组和引用数组的hashCode计算。

示例:java.util.Arrays.hashCode(long[])的具体实现

public static int hashCode(long a[]) {
        if (a == null)
            return 0;
 
        int result = 1;
        for (long element : a) {
            int elementHash = (int)(element ^ (element >>> 32));
            result = 31 * result + elementHash;
        }
 
        return result;
}
5、equals()和==的区别总结

1)对于字符串变量
equals()比较的是两个对象的内容,内容相同则返回true。至于“==”,比较的是内存中的首地址,所以如果不是同一个对象,“==”返回false。
2)对于非字符串变量
equals()比较的是内存的首地址,这时和“==”是一样的,即比较两边指向的是不是同一对象。

本文部分内容来自:
https://www.cnblogs.com/xdp-gacl/p/3637073.html
https://www.jianshu.com/p/75d9c2c3d0c1
https://www.cnblogs.com/yaobolove/p/5086510.html
https://www.cnblogs.com/thinkleesion/p/4018820.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

星空是梦想

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值