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() 的问题
HashSet 和 HashMap 的底层原理:
- 先计算 hashCode() 确定存储位置(桶)
- 再调用 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 的黄金法则。