必须重写equals和hashCode才能保证集合逻辑正确;HashSet/HashMap依赖二者协同工作,ArrayList/LinkedList的contains也依赖equals;不重写会导致判等失效。
在 Java 集合中存对象,
不一定必须重写
,但一旦你依赖集合的逻辑判断(比如
、
、
去重、
查键),而默认的
(继承自
,比较引用)不符合你的业务语义,那就
必须重写
—— 否则行为会出错。
为什么
/
要求重写
和
这两个类底层靠
定位桶位置,再用
确认是否为同一元素。如果只重写
不重写
,相同逻辑对象可能被散列到不同桶里,导致
返回
;反之,只重
不重
,可能把不同对象误判为相等。
必须满足:如果
为
,则
反过来不成立:
相同,
不一定为
(哈希冲突允许)
所有字段参与
判断的,原则上也应参与
计算(常用
)
/
的
为什么也依赖
它们的
方法内部是遍历并调用
,最终落到对象的
方法上。如果你没重写,就比较内存地址 —— 即使两个对象字段完全一样,只要不是同一个实例,
就返回
。
什么情况下可以不重写
?
仅当你明确满足以下任一条件时,可跳过:
Find JSON Path Online
Easily find JSON paths within JSON objects using our intuitive Json Path Finder
下载
立即学习
“
Java免费学习笔记(深入)
”;
该类只作为“临时数据载体”,从不放入任何需要逻辑判等的集合(如仅用于 JSON 序列化后丢弃)
你始终用
或显式字段对比(如
)
该类是不可变且单例的(如枚举),或你确保永远只用同一实例
使用 Lombok 的
自动生成(但要注意:它默认包含所有非静态非瞬态字段,需确认字段语义是否合理)
重写
的常见陷阱
最容易漏掉的不是“要不要写”,而是“怎么写才安全”:
忘记判空:
和
是基础守门员
类型检查写成
(严格类型)还是
(允许子类)?多数场景用
更实用
字段比较时对
敏感:用
替代
,避免 NPE
修改了影响
的字段后,又把它放在
或作为
键 —— 这会导致对象“消失”(找不到、删不掉),因为哈希桶位置已失效
最常被忽略的一点:重写
很容易,但一旦对象进了
的 key 或
,后续就不该再修改参与判等的字段 —— 这不是规范建议,是硬性约束。
equalscontainsremoveHashSetHashMapequalsObjectHashSetHashMapequalshashCodehashCodeequalsequalshashCodecontainsfalsehashCodeequalshashCodea.equals(b)truea.hashCode() == b.hashCode()hashCodeequalstrueequalshashCodeObjects.hash(...)ArrayListLinkedListcontainsequalscontainsObjects.equals(element, e)equalscontainsfalsepublic class User {
private String name;
private int age;
public User(String name, int age) { this.name = name; this.age = age; }
// 没重写 equals → 默认用 Object.equals()
}
List list = new ArrayList<>();
list.add(new User("Alice", 25));
System.out.println(list.contains(new User("Alice", 25))); // false! equals==user.getName().equals(other.getName()) && user.getAge() == other.getAge()@EqualsAndHashCodeequalsif (obj == null) return false;if (this == obj) return true;getClass() != obj.getClass()!(obj instanceof User)instanceofnullObjects.equals(a, b)a.equals(b)equalsHashSetHashMapequalsHashMapHashSet