Java的equals(),hashCode()应该在什么时候重写

package day2.homework3;import java.util.HashSet;import java.util.Scanner;public class Test { public static void main(String[] args) { HashSet hashSet = new HashSet<>(); Scanner sc = new Scanner(System.in); while (true) { System.out.println("请输入用户名:"); String username = sc.next(); System.out.println("请输入密码:"); String password = sc.next(); User user = new User(username, password); if(hashSet.add(user)){ System.out.println("注册成功"); } else{ System.out.println("用户名已重复"); } } }}

有什么问题

这段代码存在一个严重问题HashSet<User> 无法正确判断 User 对象是否重复。

核心问题:

HashSet 判断重复的依据是 hashCode() 和 equals() 方法 ,但 User 类没有重写这两个方法,导致:

  • 即使两个 User 对象的 username 相同,HashSet 也会认为它们是不同的对象
  • 永远不会有"用户名已重复"的提示

问题演示:

java 复制代码
// 假设User类没有重写hashCode和equals
User user1 = new User("张三", "123");
User user2 = new User("张三", "456");

hashSet.add(user1);  // 返回true
hashSet.add(user2);  // 也会返回true(因为hashCode不同)
// 结果:同一个用户名"张三"被添加了两次!

解决方案:

需要在 User 类中重写 hashCode()equals() 方法,只根据 username 判断是否重复

java 复制代码
import java.util.Objects;

public class User {
    private String username;
    private String password;
    
    public User(String username, String password) {
        this.username = username;
        this.password = password;
    }
    
    // getter和setter方法
    public String getUsername() {
        return username;
    }
    
    public void setUsername(String username) {
        this.username = username;
    }
    
    public String getPassword() {
        return password;
    }
    
    public void setPassword(String password) {
        this.password = password;
    }
    
    // 重写equals方法:只根据username判断是否相等
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        User user = (User) o;
        return Objects.equals(username, user.username);
    }
    
    // 重写hashCode方法:只根据username生成哈希码
    @Override
    public int hashCode() {
        return Objects.hash(username);
    }
    
    @Override
    public String toString() {
        return "User{username='" + username + "', password='" + password + "'}";
    }
}

完整改进版代码:

java 复制代码
package day2.homework3;

import java.util.HashSet;
import java.util.Scanner;

public class Test {
    public static void main(String[] args) {
        HashSet<User> hashSet = new HashSet<>();
        Scanner sc = new Scanner(System.in);
        
        while (true) {
            System.out.println("请输入用户名:");
            String username = sc.next();
            System.out.println("请输入密码:");
            String password = sc.next();
            
            User user = new User(username, password);
            
            if (hashSet.add(user)) {
                System.out.println("注册成功");
                System.out.println("当前用户列表:" + hashSet);
            } else {
                System.out.println("用户名已重复,请重新输入!");
            }
            
            System.out.println("--------------------");
        }
    }
}

运行效果:

text

复制代码
请输入用户名:
张三
请输入密码:
123
注册成功
当前用户列表:[User{username='张三', password='123'}]
--------------------
请输入用户名:
李四
请输入密码:
456
注册成功
当前用户列表:[User{username='张三', password='123'}, User{username='李四', password='456'}]
--------------------
请输入用户名:
张三
请输入密码:
789
用户名已重复,请重新输入!
--------------------

知识点总结:

1. HashSet 去重原理

  • 先调用 hashCode() 比较哈希值
  • 如果哈希值相同,再调用 equals() 比较内容
  • 只有 hashCode()equals() 都相同,才认为是重复元素

2. 为什么要重写 hashCode() 和 equals()

  • 默认的 equals() :比较对象的内存地址(==
  • 默认的 hashCode():返回对象的内存地址转换的整数
  • 需要根据业务逻辑(如用户名)自定义比较规则

3. 重写规范

  • 如果重写 equals(),必须重写 hashCode()
  • 两个对象 equals()true 时,hashCode() 必须相等
  • 使用 Objects.hash() 工具方法生成哈希码

其他改进建议:

java 复制代码
// 1. 添加退出功能
while (true) {
    System.out.println("请输入用户名(输入exit退出):");
    String username = sc.next();
    if ("exit".equalsIgnoreCase(username)) {
        System.out.println("退出系统");
        break;
    }
    // ... 其余代码
}

// 2. 添加用户名和密码的非空验证
if (username == null || username.trim().isEmpty()) {
    System.out.println("用户名不能为空!");
    continue;
}

// 3. 添加密码强度验证
if (password.length() < 6) {
    System.out.println("密码长度不能少于6位!");
    continue;
}

常见面试题:

Q:HashSet 和 HashMap 如何判断元素重复?

A:

  • HashSet:先比较 hashCode(),再比较 equals()
  • HashMap:先比较 key 的 hashCode(),再比较 key 的 equals()

Q:不重写 hashCode() 会有什么后果?

A:即使两个对象的 equals() 返回 true,但由于 hashCode() 不同,HashSet 会认为它们是不同的对象,导致无法去重,也可能导致 HashMap 无法正确获取 value。

这就是为什么在使用 HashSet 或作为 HashMap 的 key 时,必须重写 hashCode()equals() 方法!

如果只重写equals或hashCode不行吗

不行!必须同时重写 equals() 和 hashCode(),否则会导致严重问题。

为什么必须同时重写?

1. 只重写 equals() 的问题

HashSetHashMap 的底层原理:

  1. 先计算 hashCode() 确定存储位置(桶)
  2. 再调用 equals() 比较是否相同

如果只重写 equals() 而不重写 hashCode()

java 复制代码
// 错误示例:User类只重写equals
public class User {
    private String username;
    
    @Override
    public boolean equals(Object o) {
        User user = (User) o;
        return Objects.equals(username, user.username);
    }
    // 没有重写hashCode()
}

// 测试代码
HashSet<User> set = new HashSet<>();
User user1 = new User("张三");
User user2 = new User("张三");

set.add(user1);  // hashCode1 = 123456
set.add(user2);  // hashCode2 = 789012(不同!)

System.out.println(set.size());  // 输出2(错误!应该是1)

结果 :因为 hashCode() 不同,对象被放到不同的桶中,根本不会调用 equals() 比较,导致重复!

2. 只重写 hashCode() 的问题

如果只重写 hashCode() 而不重写 equals()

java 复制代码
// 错误示例:User类只重写hashCode
public class User {
    private String username;
    
    @Override
    public int hashCode() {
        return Objects.hash(username);
    }
    // 没有重写equals()
}

// 测试代码
HashSet<User> set = new HashSet<>();
User user1 = new User("张三");
User user2 = new User("张三");

set.add(user1);  // hashCode相同(都等于50000)
set.add(user2);  // hashCode相同,调用equals()比较

// 默认的equals()比较内存地址
// user1和user2是不同的对象,地址不同
// equals()返回false,所以认为是不同元素

System.out.println(set.size());  // 输出2(错误!应该是1)

结果 :虽然 hashCode() 相同,但 equals() 返回 false,仍然认为是不同对象!

完整测试对比:

java 复制代码
import java.util.HashSet;
import java.util.Objects;

// 测试类
public class HashCodeEqualsTest {
    
    // 情况1:都不重写
    static class UserNone {
        String username;
        UserNone(String username) { this.username = username; }
    }
    
    // 情况2:只重写equals
    static class UserOnlyEquals {
        String username;
        UserOnlyEquals(String username) { this.username = username; }
        @Override
        public boolean equals(Object o) {
            UserOnlyEquals that = (UserOnlyEquals) o;
            return Objects.equals(username, that.username);
        }
    }
    
    // 情况3:只重写hashCode
    static class UserOnlyHashCode {
        String username;
        UserOnlyHashCode(String username) { this.username = username; }
        @Override
        public int hashCode() {
            return Objects.hash(username);
        }
    }
    
    // 情况4:都重写(正确)
    static class UserCorrect {
        String username;
        UserCorrect(String username) { this.username = username; }
        @Override
        public boolean equals(Object o) {
            UserCorrect that = (UserCorrect) o;
            return Objects.equals(username, that.username);
        }
        @Override
        public int hashCode() {
            return Objects.hash(username);
        }
    }
    
    public static void main(String[] args) {
        // 测试1:都不重写
        HashSet<UserNone> set1 = new HashSet<>();
        set1.add(new UserNone("张三"));
        set1.add(new UserNone("张三"));
        System.out.println("都不重写 - HashSet大小: " + set1.size());  // 2 ❌
        
        // 测试2:只重写equals
        HashSet<UserOnlyEquals> set2 = new HashSet<>();
        set2.add(new UserOnlyEquals("张三"));
        set2.add(new UserOnlyEquals("张三"));
        System.out.println("只重写equals - HashSet大小: " + set2.size());  // 2 ❌
        
        // 测试3:只重写hashCode
        HashSet<UserOnlyHashCode> set3 = new HashSet<>();
        set3.add(new UserOnlyHashCode("张三"));
        set3.add(new UserOnlyHashCode("张三"));
        System.out.println("只重写hashCode - HashSet大小: " + set3.size());  // 2 ❌
        
        // 测试4:都重写
        HashSet<UserCorrect> set4 = new HashSet<>();
        set4.add(new UserCorrect("张三"));
        set4.add(new UserCorrect("张三"));
        System.out.println("都重写 - HashSet大小: " + set4.size());  // 1 ✅
    }
}

运行结果:

text

复制代码
都不重写 - HashSet大小: 2
只重写equals - HashSet大小: 2
只重写hashCode - HashSet大小: 2
都重写 - HashSet大小: 1

HashMap 同样的问题:

java 复制代码
// 只重写equals的User类作为HashMap的key
HashMap<UserOnlyEquals, String> map = new HashMap<>();
UserOnlyEquals key1 = new UserOnlyEquals("张三");
UserOnlyEquals key2 = new UserOnlyEquals("张三");

map.put(key1, "value1");
map.put(key2, "value2");

System.out.println(map.get(key1));  // value1
System.out.println(map.get(key2));  // value2
System.out.println(map.size());     // 2 ❌(应该是1)

// 更严重的问题:无法获取!
System.out.println(map.get(new UserOnlyEquals("张三")));  // null ❌

官方规范(Java Object 类约定):

java 复制代码
/**
 * 1. 如果两个对象 equals() 为 true,则 hashCode() 必须相等
 * 2. 如果两个对象 equals() 为 false,hashCode() 不一定不等
 * 3. 如果 hashCode() 相等,equals() 不一定为 true
 * 4. 如果 hashCode() 不等,equals() 必须为 false
 */

正确的重写方式:

java 复制代码
public class User {
    private String username;
    private String password;
    
    // 推荐方式1:使用 Objects 工具类
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() o.getClass()) return false;
        User user = (User) o;
        return Objects.equals(username, user.username);
    }
    
    @Override
    public int hashCode() {
        return Objects.hash(username);
    }
    
    // 推荐方式2:手动实现(性能更好)
    // @Override
    // public boolean equals(Object o) {
    //     if (this == o) return true;
    //     if (o == null || getClass() != o.getClass()) return false;
    //     User user = (User) o;
    //     return username != null ? username.equals(user.username) : user.username == null;
    // }
    // 
    // @Override
    // public int hashCode() {
    //     return username != null ? username.hashCode() : 0;
    // }
}

总结:

重写情况 HashSet去重 HashMap key查找 是否符合规范
都不重写 ❌ 失败 ❌ 失败
只重写equals ❌ 失败 ❌ 失败(部分情况)
只重写hashCode ❌ 失败 ❌ 失败
都重写 ✅ 成功 ✅ 成功

结论 :必须同时重写 equals()hashCode(),缺一不可!这是 Java 的黄金法则。

相关推荐
REDcker1 小时前
有限状态机与状态模式详解 FSM建模Java状态模式与C++表驱动模板实践
java·c++·状态模式
2301_803934611 小时前
Go语言如何做网络爬虫_Go语言爬虫开发教程【指南】
jvm·数据库·python
盲敲代码的阿豪1 小时前
Python 入门基础教程(爬虫前置版)
开发语言·爬虫·python
你的保护色2 小时前
【无标题】
java·服务器·网络
basketball6162 小时前
C++ 构造函数完全指南:从入门到进阶
java·开发语言·c++
互联科技报2 小时前
2026超融合选型:Top5品牌与市场格局解读
开发语言·perl
weixin199701080162 小时前
[特殊字符] 智能数据采集:数字化转型的“数据石油勘探队”(附Python实战源码)
开发语言·python
淘矿人2 小时前
Claude辅助DevOps实践
java·大数据·运维·人工智能·算法·bug·devops
想唱rap2 小时前
IO多路转接之poll
服务器·开发语言·数据库·c++