canequal模式通过引入可重写的canequal方法显式控制对象间可比性,解决继承中equals的对称性与传递性冲突;父类默认允许比较,子类重写时仅对自身及子类实例返回true,并需同步更新hashcode。

CanEqual 模式是专为解决继承结构中 equals 方法难以兼顾对称性与传递性的设计技巧,核心在于**显式控制“可比较性”**,而不是让子类盲目接受父类的相等判断逻辑。
为什么继承下 equals 容易出错
典型问题出现在子类添加新字段后:若直接用 instanceof 判断类型,会导致不对称(父类实例 equals 子类实例返回 true,反过来却返回 false);若用 getClass() == obj.getClass(),又会切断父子对象间的合理相等关系(比如两个语义相同的 Point 和 ColorPoint 无法相等)。
CanEqual 的核心机制
它引入一个受保护的、可被子类重写的 canEqual(Object other) 方法,用于声明“当前对象是否允许与 other 实例进行内容比较”。父类默认返回 true,子类重写时只对自身类型或更具体类型返回 true。
- 父类中
equals先调用other.canEqual(this),再做字段比对 - 子类重写
canEqual:仅当other是本类或其子类实例时才返回true - 这样既保证对称性(双方都需认可可比),又避免跨层级误判
一个简洁实现示例
父类 Point:
Java项目代码review工具。分析Git变更+完整调用链路上下文,推断业务需求,进行多维度评分和分类汇总,生成完整PRD文档。包含细粒度Java代码审查清单(Null安全、异常处理、Streams、并发、equals/hashCode、资源管理、API设计、性能、MyBatis/ORM、事务边界、SQL/DD...
public class Point {
private final int x, y;
// 构造器、getter 省略
protected boolean canEqual(Object other) {
return other instanceof Point;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || !canEqual(obj)) return false;
Point that = (Point) obj;
return x == that.x && y == that.y;
}
}
子类 ColorPoint:
public class ColorPoint extends Point {
private final String color;
// 构造器、getter 省略
@Override
protected boolean canEqual(Object other) {
return other instanceof ColorPoint; // 只允许同类型比较
}
@Override
public boolean equals(Object obj) {
if (!super.equals(obj)) return false;
if (!(obj instanceof ColorPoint)) return false;
ColorPoint that = (ColorPoint) obj;
return Objects.equals(color, that.color);
}
}
此时:new ColorPoint(1,2,"red").equals(new Point(1,2)) → false(因为 Point 的 canEqual 虽为 true,但 ColorPoint.canEqual(new Point(...)) 返回 false);而 new Point(1,2).equals(new ColorPoint(1,2,"blue")) 同样为 false,满足对称性。
配套必须重写 hashCode
只要 equals 逻辑变了,hashCode 就必须同步更新,且只基于参与 equals 判断的字段计算。例如 ColorPoint 的 hashCode 应同时包含 x、y 和 color,否则在 HashSet 中会出现“能存不能查”的问题。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










