hashcode与equals之间区别与重写

本文详细探讨了Java中hashcode与equals的区别,包括如何获取对象内存地址、equals方法的特性、重写equals与hashCode的原因和规则。通过示例展示了不重写时可能导致的问题,如在Set或Map中无法正确区分相等对象。同时,介绍了Lombok库如何简化equals和hashCode的重写过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

hashcode与equals

Java 如何获取对象内存地址

maven引入依赖包

<dependency>
    <groupId>org.openjdk.jol</groupId>
    <artifactId>jol-core</artifactId>
    <version>0.9</version>
</dependency>

示例代码

//获取两个对象的内存地址
System.out.println("user1的内存地址:"+ VM.current().addressOf(user1));
System.out.println("user2的内存地址:"+ VM.current().addressOf(user2));

Java 语言规范要求 equals 方法具有下面的特性:

  • 自反性:对于任何非空引用 x,x.equals(x) 应该返回 true。
  • 对称性:对于任何引用 x 和 y,当且仅当 y.equals(x) 返回 true,x.equals(y) 也应该返回 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

为什么重写equals方法后,要重写hashcode方法?(equals和hashCode间的通用约定)

如果覆写了equals方法,一般也要覆写hashCode方法,因为equals方法和hashCode方法间存在通用约定:

  • 如果一个对象的 equals 方法做比较所用到的信息没有被修改的话,那么对该对象调用 hashCode 方法多次,它必须始终返回同一个整数,在同一个应用程序的多次执行过程中,这个整数可以不同
  • 如果两个对象根据 equals(Object) 方法是相等的,那么这两个对象的 hashCode 方法返回的整数值必然相等;
  • 如果两个对象根据 equals(Object) 方法是不相等的,那么这两个对象的 hashCode 方法返回的整数值有可能相等(Hash冲突导致)

Java Object equals()方法

  1. equals方法的源代码
public boolean equals(Object obj) {
    return (this == obj);
}

以上这个方法是Object类的默认实现

  1. SUN公司设计equals()方法的目的

    以后编程过程中,都要通过equals方法来判断两个对象是否相等。equals方法是判断两个对象是否相等的。

  2. 我们为什么要重写equals()方法?

    • Object类这个默认的equals()方法不够用
    • 在Object类中的equals方法中,默认采用的是==判断两个java对象是否相等。而==判断的是两个java对象的内存地址.我们应该判断两个Java对象的内容是否相等。所以Object的equals方法不够用,需要重写子类的equals()方法。

    equals()方法比较两个对象,是判断两个对象的引用指向同一个对象,即比较两个对象的内存地址是否相等

代码示例:

  • 创建一个User对象

    public class User {
    
        private String name;
        private Integer sex;
    
        public User(String name,Integer sex){
            this.name = name;
            this.sex = sex;
        }
        public User(){}
    }
    
  • 未重写equals()方法

    public class ObjectEqualsTest {
        public static void main(String[] args) {
            //判断两个基本数据类型的数据是否相等直接使用==
            int a = 100;
            int b = 100;
            //这个==是判断a中保存的100和b保存的100是否相等。
            System.out.println(a==b);
    
            //判断两个Java对象是否相等,我们怎么办?能直接使用==吗?
            //创建一个用户对象是:姓名:张三;性别:1(男)2(女)
            User user1 = new User("张三",1);
            //创建一个新的用户对象是:姓名:张三;性别:1(男)2(女)
            User user2 = new User("张三", 1);
    
            //获取两个对象的内存地址
            System.out.println("user1的内存地址:"+ VM.current().addressOf(user1));
            System.out.println("user2的内存地址:"+ VM.current().addressOf(user2));
    
            //测试如下,比较两个对象是否相等,能不能使用“==”?
            //这里的“==”判断的是:user1中保存的对象内存地址和user2保存的对象内存地址是否相等
            System.out.println("user1==user2:"+(user1==user2));
    
            //重写equals方法之前
            System.out.println("重写equals方法之前:"+user1.equals(user2));
        }
    }
    

    运行结果:

    true

    user1的内存地址:30477892064
    user2的内存地址:30477892136
    user1==user2:false
    重写equals方法之前:false

总结:判断对象是否相等,不能使用“==”,因为比较的是两个对象的内存地址。


覆写equals方法和hashCode方法要遵循的规则

如何覆写equals方法(步骤最好严格遵循)

  1. 入参为Object类型,变量名多为otherObject;
  2. 检查this和otherObject的引用指向的是否是同一个对象,同一个对象返回true,否则继续进行下面的步骤;
  3. 检查otherObject是否为null,是null则返回false,否则继续进行下面的步骤;
  4. 比较 this 与 otherObject 是否属于同一个类,用instanceOf或者getClass,不是同一个类则返回false,否则继续进行下面的步骤;
  5. 将otherObject强制转换为this的类型,然后一一进行各成员属性的比较

代码示例

重写equals方法

@Override
public boolean equals(Object obj) {
   if(this==obj) return true;
   if(obj==null || getClass()!=obj.getClass()) return false;
   User user = (User) obj;
   return Objects.equals(name,user.name) && Objects.equals(sex,user.sex);
}

执行代码:为重写hashcode

//重写equals方法之后
System.out.println("重写equals方法之后:"+user1.equals(user2));

//未重写hashcode方法之前
System.out.println("user1的hashcode:"+user1.hashCode());
System.out.println("user2的hashcode:"+user2.hashCode());

运行结果

重写equals方法之后:true
user1的hashcode:1177377518
user2的hashcode:1773206895

user1.equals(user2)方法已经返回true了,说明我们覆写的equals方法生效了,但hashCode方法返回的值还是不一样,由于存在equals和hashCode间的通用约定,因此我们覆写equals方法的同时,也要覆写hashCode方法


如何覆写hashCode方法

  1. 初始化一个int类型的result,这个result返回最终的hashCode值;
  2. 对类中的每个成员属性,进行下面的操作,并将结果累加起来一并组成result,操作如下:
  • 将成员属性的值转化为int型,比如String类型的成员属性可以调用String里写好的hashCode方法;
  • 将成员属性转化好的int类型的值乘以31,然后累加:result += intValue * 31;
  1. 如果该域是一个对象引用,并且该类的 equals 方法通过递归调用 equals 的方式来比较这个域,则同样对这个域递归调用 hashCode。如果要求一个更为复杂的比较,则为这个域计算一个 “规范表示”,然后针对这个范式表示调用 hashCode。如果这个域的值为 null,则返回 0(或者其他某个常数)。

  2. 如果该域是一个数组,则把每一个元素当做单独的域来处理。也就是说,递归地应用上述规则,对每个重要的元素计算一个散列码,然后根据下面的做法把这些散列值组合起来。

为什么 String hashCode 方法选择数字31作为乘子

代码示例

如果不重写hashcode,那么在Set或Map中会发生什么?

Set<User> userSet = new HashSet<>();
userSet.add(user1);
userSet.add(user2);
System.out.println("set的长度:"+userSet.size());

Map<User,String> userStringMap = new HashMap<>();
userStringMap.put(user1,"a");
userStringMap.put(user2,"a");
System.out.println("map的长度:"+userStringMap.size());

运行结果

set的长度:2
map的长度:2

两个User对象是相等的,所以说两个相等的对象放到Set里与Map里是不符合预期的,所以这就产生一个错误

Set区分两个对象是否相等:是看hashcode是否相等,然后再去判断两个对象的equals

Map根据key值的hashcode分配与获取对象数组保存的下标,然后再去根据equals方法去区分相等,也就是key是否唯一。

如果想要相同的User对象在Map或Set中只有一个,只有重写hashcode方法,为了解决这个问题,我们需要对User对象重写hashcode方法。

重写User对象hashcode

@Override
public int hashCode() {
    int result = name!=null?name.hashCode():0;
    result = 31*result+(sex!=null?sex.hashCode():0);
    result = 31*result;
    return result;
}

执行代码

//重写hashcode方法之后
System.out.println("user1的hashcode:"+user1.hashCode());
System.out.println("user2的hashcode:"+user2.hashCode());

运行结果

set的长度:1
map的长度:1
user1的hashcode:744668360
user2的hashcode:744668360


lombok实现equals方法和hashCode方法

可以看到上面介绍的覆写equals方法和hashCode方法很繁琐,如果我们每定义一个entity都要覆写这两个方法,一是麻烦,而是代码写的太冗余了,这时候插件lombok就发挥作用了,lombok支持我们在代码中通过注解来自动为我们的entity类创造很多基本方法,比如getter/setter,这些方法中包括了equals方法和hashCode方法,代码里具体使用如下

  • maven方式添加lombok项目依赖

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.20</version>
        <scope>provided</scope>
    </dependency>
    
  • 添加注解:@Data@EqualsAndHashCode

    import lombok.Data;
    import lombok.EqualsAndHashCode;
    
    @Data
    @EqualsAndHashCode(exclude = {"score","time"})
    public class User {
    
        private String name;
        
        private Integer sex;
    
        public User(String name,Integer sex){
            this.name = name;
            this.sex = sex;
        }
        public User(){}
    }
    

    可以使用逗号分隔,排除不想比对的数据。

    通过在实体类前加上@Data,@EqualsAndHashCode注解,即可为实体类自动生成equals方法和hashCode方法,执行结果如下:

    重写equals方法之后:true
    user1的hashcode:778429
    user2的hashcode:778429

    equals方法返回结果为true,hashcode方法返回值相等,只是与我们自己写的方法返回的hash值不同,但是代码量大大减少了,有时间详细介绍一下lombok的使用。

比较两个对象调用顺序

  1. 先比较hashCode,如果不相等,则这两个对象不相等,相等则进入下一步;
  2. 调用equals方法,如果equal()也相同,则表示这两个对象是真的相同,否则不相等。

Java String equals()方法

  • equals()源代码

    public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        if (anObject instanceof String) {
            String aString = (String)anObject;
            if (coder() == aString.coder()) {
                return isLatin1() ? StringLatin1.equals(value, aString.value)
                    : StringUTF16.equals(value, aString.value);
            }
        }
        return false;
    }
    

总结:String类已经重写了equals方法,比较两个字符串不能使用“==”,必须使用equals。

  • 代码示例

    public class StringEqualsTest {
        public static void main(String[] args) {
            //大部分情况下,采用这样的方式创建字符串对象
            String str1 = "hello";
            String str2 = "world";
    
            //实际上String也是一个类,不是基本类型,存在构造方法
            String test1 = new String("test1");
            String test2 = new String("test1");
    
            //new两次,两个对象的内存地址不同
            System.out.println("test1内存地址:"+ VM.current().addressOf(test1));
            System.out.println("test2内存地址:"+ VM.current().addressOf(test2));
            //“==”判断两个对象内存地址,不是内容
            System.out.println("test1==test2: "+(test1==test2));
    
            //比较两个字符串是否相等能不能使用==?
            //不能,必须使用equals()
            //String类已经重写equals()
            System.out.println("test1.equals(test2): "+test1.equals(test2));
        }
    }
    

    运行结果

    test1内存地址:30476430520
    test2内存地址:30476430592
    test1==test2: false
    test1.equals(test2):true

结论:

  • java中什么类型的数据使用“==”判断
    • Java中基本数据类型比较是否相等,使用==
  • Java中什么类型的数据需要使用equals判断
    • Java中所有的引用数据类型统一使用equals方法判断是否相等。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值