重写 tostring()、equals() 和 hashcode() 时需遵循规范:tostring() 应简洁安全,避免异常、循环引用和敏感信息;equals() 与 hashcode() 必须同时重写,且逻辑一致;优先使用 objects.equals() 和 objects.hash();注意基本类型用 ==,bigdecimal 等用 compareto()。

Object.toString() 不重写就只能看到哈希码
默认的 toString() 返回的是 类名@十六进制哈希值,比如 Person@1b6d3586。这在日志、调试、单元测试断言里完全没法读,更别提前端展示或 JSON 序列化时的可读性了。
重写时注意三点:
- 不要在
toString()里抛异常(比如调用可能为null的字段未判空)——它可能被任意地方隐式调用,比如System.out.println(obj)或日志框架格式化 - 避免递归引用:如果对象 A 持有 B,B 又持有 A,直接拼接会导致
StackOverflowError - 生产环境慎用敏感字段:比如
User.toString()里不小心打印了password字段,日志一打就泄露
示例(安全简洁版):
@Override<br>public String toString() {<br> return "Person{" +<br> "name='" + Objects.toString(name, "null") + '\'' +<br> ", age=" + age +<br> '}';<br>}
equals() 必须和 hashCode() 一起重写
只重写 equals() 而不重写 hashCode(),会导致对象在 HashMap、HashSet 中行为异常:两个逻辑相等的对象可能被存到不同桶里,查不到、去不了重、甚至“消失”。JVM 不会报错,但 bug 很隐蔽。
重写要点:
- 先用
==判自反性(同一对象直接返回true) - 用
instanceof或getClass() == obj.getClass()判类型,推荐后者——避免子类重写后破坏对称性 - 字段比较用
Objects.equals(a, b),自动处理null,别手写a == null ? b == null : a.equals(b) -
hashCode()必须用和equals()相同的非静态字段计算,且用Objects.hash(f1, f2, ...)生成
错误示范:
你接手一个需求时,是不是经常这样:"改一个小字段 → 结果把不相关的代码也改了"、"需求说得很模糊 → 做着做着发现跑偏了"、"数据库加个字段 → 上线才发现漏了数据兼容"、"做了个新功能 → 一个月后自己都忘了当时怎么设计的"、"写到一半被中断 → 再回来不知道从哪继续";这个 Skill 就是专门解决这些问题的。 它会自动探测你的项目环境(JDK、Spring Boot、ORM、数据库、前端框架),记在
// ❌ 只重写了 equals,没碰 hashCode<br>@Override<br>public boolean equals(Object o) {<br> if (this == o) return true;<br> if (o == null || getClass() != o.getClass()) return false;<br> Person person = (Person) o;<br> return age == person.age && Objects.equals(name, person.name);<br>}
IDE 自动生成的 equals/hashCode/toString 不是万能的
IntelliJ 或 Eclipse 生成的代码看起来很全,但容易埋坑:
- 字段选漏:比如新增了
status字段但没加进equals(),导致两个“实际相同”的对象被判为不等 - 用了不该比的字段:比如数据库主键
id是自增的,新建对象id=0,保存后变成id=123,这时用id参与equals()就会让新旧对象永远不等 -
toString()包含大集合或 IO 调用:生成时勾选了某个List<detail></detail>字段,结果每次打印都触发懒加载或深遍历,性能骤降
建议:生成后立刻检查字段列表,把业务上“定义相等性”的字段列清楚,其余一律排除;对集合字段,改用 details.size() 或 details.isEmpty() 替代完整打印。
String、Integer 等包装类的 equals 已经可靠,别自己造轮子
常见误区是给 String 字段手动判空再比较:
if (name == null ? other.name == null : name.equals(other.name))其实完全没必要。直接用
Objects.equals(name, other.name) 更安全、更短、语义更清晰。
同理:
-
Integer、LocalDateTime等标准类的equals()都已按值语义实现,无需额外包装 - 别为了“统一风格”把所有字段都改成
Objects.equals(a, b)—— 基本类型(int、boolean)必须用==,否则编译不过 -
BigDecimal和BigInteger要小心:它们的equals()比较的是值和精度(new BigDecimal("1.0").equals(new BigDecimal("1"))返回false),业务上需要值相等就用compareTo() == 0
最常被忽略的一点:重写 equals() 后,如果这个类要作为 TreeSet 或 TreeMap 的 key,还得确保实现了 Comparable,或者传入外部 Comparator——因为 Tree* 不看 equals(),只认 compareTo() 结果。这点和 HashSet 完全不同,很容易踩空。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










