当前位置: 首页 > news >正文

Java高效编程(10):重写equals时必须遵循通用约定

解锁Python编程的无限可能:《奇妙的Python》带你漫游代码世界

重写 equals 方法看似简单,但极易出错,且后果严重。要避免问题,最简单的方法就是不重写 equals,这样每个类的实例只与自身相等。如果以下情况适用,那么不重写 equals 是正确的选择:

  1. 类的每个实例都是独一无二的:例如 Thread 类,其代表的是活跃实体,而不是值,默认的 equals 方法已经适用。
  2. 类不需要逻辑上的相等性测试:例如 Pattern 类可以重写 equals 来检查两个正则表达式是否相同,但设计者认为不需要这个功能。
  3. 超类已经重写了 equals 并且该行为适用于子类:例如,大多数 Set 实现继承自 AbstractSetequalsList 实现继承自 AbstractListMap 实现继承自 AbstractMap
  4. 类是私有或包私有的,且你确定 equals 不会被调用:可以通过重写 equals 方法抛出 AssertionError 来确保该方法不会被意外调用。

何时应该重写 equals

当类有逻辑上的相等性要求且超类没有重写 equals 时,就应该重写。通常这是值类的情况,例如 IntegerString。程序员期望通过 equals 比较这些值类的逻辑等价性,而不是对象的引用是否相同。重写 equals 方法不仅是为了满足程序员的预期,也是为了让实例在作为键或集合元素时表现一致且可预测。

不需要重写 equals 的情况之一是使用实例控制(【条目1】)确保每个值最多只有一个对象。例如 Enum 类型,因为逻辑等价就是对象标识,Objectequals 方法已足够。

equals 合同

重写 equals 时,必须遵循 equals 的一般合同。合同规定 equals 实现等价关系,具体包括以下五个属性:

  1. 自反性:对于任何非空引用值 xx.equals(x) 必须返回 true
  2. 对称性:对于任何非空引用值 xyx.equals(y) 必须返回与 y.equals(x) 相同的结果。
  3. 传递性:如果 x.equals(y) 为真,且 y.equals(z) 为真,那么 x.equals(z) 必须为真。
  4. 一致性:只要 equals 比较中使用的信息没有被修改,x.equals(y) 的多次调用必须始终返回相同的结果。
  5. 非空性:对于任何非空引用值 xx.equals(null) 必须返回 false

这些要求听起来复杂,但理解后不难遵守。如果违反合同,可能导致程序行为不稳定,甚至崩溃,且错误源头难以定位。

详细解释 equals 合同

自反性

自反性要求对象必须与自身相等。违反这一要求很少见。如果违反它,可能导致集合中的 contains 方法无法找到刚添加的对象。

对称性

对称性要求两个对象必须对其相等性意见一致。例如,以下类实现了一个忽略大小写的字符串:

// 错误示例 - 违反对称性
public final class CaseInsensitiveString {private final String s;public CaseInsensitiveString(String s) {this.s = Objects.requireNonNull(s);}@Override public boolean equals(Object o) {if (o instanceof CaseInsensitiveString)return s.equalsIgnoreCase(((CaseInsensitiveString) o).s);if (o instanceof String)return s.equalsIgnoreCase((String) o);return false;}
}

上面的 equals 方法试图与普通字符串互操作,但这导致了对称性问题:cis.equals(s) 返回 true,而 s.equals(cis) 返回 false。为解决此问题,可以去掉与 String 的互操作代码:

@Override public boolean equals(Object o) {return o instanceof CaseInsensitiveString &&((CaseInsensitiveString) o).s.equalsIgnoreCase(s);
}
传递性

传递性要求如果 x.equals(y) 为真且 y.equals(z) 为真,那么 x.equals(z) 也必须为真。考虑扩展类 Point 添加颜色属性的场景:

public class ColorPoint extends Point {private final Color color;public ColorPoint(int x, int y, Color color) {super(x, y);this.color = color;}@Override public boolean equals(Object o) {if (!(o instanceof ColorPoint))return false;return super.equals(o) && ((ColorPoint) o).color == color;}
}

此方法违反了对称性和传递性:p.equals(cp) 返回 true,而 cp.equals(p) 返回 false。为解决此问题,使用组合而不是继承:

public class ColorPoint {private final Point point;private final Color color;public ColorPoint(int x, int y, Color color) {point = new Point(x, y);this.color = Objects.requireNonNull(color);}@Override public boolean equals(Object o) {if (!(o instanceof ColorPoint))return false;ColorPoint cp = (ColorPoint) o;return cp.point.equals(point) && cp.color.equals(color);}
}
一致性

一致性要求对象在未被修改时,相等性必须一致。如果类是可变的,则要确保修改后 equals 的结果也保持一致。

非空性

对象必须与 null 不相等。即使传递 nullequals 返回 false,也不应抛出 NullPointerException。这可以通过 instanceof 检查来实现。

编写高质量的 equals 方法

  1. 使用 == 检查参数是否为当前对象。
  2. 使用 instanceof 检查参数类型。
  3. 将参数转换为正确类型。
  4. 比较所有“重要”字段,确保它们的值相等。

结论

不应轻易重写 equals,除非有必要。如果需要重写,务必确保比较类的所有重要字段,并遵守 equals 合同的五个规定。对于复杂的值比较,应考虑使用组合而非继承。


http://www.mrgr.cn/news/38501.html

相关文章:

  • MySQL(面试题 - 同类型归纳面试题)
  • MySQL 之多表设计详解
  • 优雅使用 MapStruct 进行类复制
  • EDM平台大比拼 用户体验与营销效果双重测评
  • 使用jdframe进行数据处理
  • 初始爬虫9
  • Mybatis(三)
  • 信道衰落的公式
  • 【大模型】AutoDL部署AI绘图大模型Stable Diffusion使用详解
  • C#从入门到精通(30)—C#Marshal类用法总结
  • Mybatis中字段返回值映射问题
  • Kubernetes学习路线
  • PCB基础
  • 传奇架设教程:传奇登录器公告窗口如何设置?link.htm网页文件制作教程
  • 备考中考的制胜法宝 —— 全国历年中考真题试卷大全
  • muduo网络库介绍
  • VB.net读写NDEF标签URI智能海报WIFI蓝牙连接
  • OpenCV视频I/O(9)视频采集类VideoCapture之释放与视频捕获相关的所有资源函数release()的使用
  • 【C++】类和对象(类的定义,类域,实例化,this指针)
  • 华为仓颉语言入门(7):深入理解 do-while 循环及其应用