重写equals时,遵守的规定
0 正确的equals方法
- public class MyClass {
- // 主要属性1
- private int primaryAttr1;
- // 主要属性2
- private int primaryAttr2;
- // 可选属性
- private int optionalAttr;
- // 延迟加载,缓存散列码
- private volatile int hashCode = 0;
- @Override
- public int hashCode() {
- if(hashCode == 0) {
- int result = 17;
- result = 37*result + primaryAttr1;
- result = 37*result + primaryAttr2;
- hashCode = result;
- }
- return hashCode;
- }
- @Override
- public boolean equals(Object obj) {
- if(obj == this) {
- return true;
- }
- if(!(obj instanceof MyClass)) {
- return false;
- }
- MyClass myClass = (MyClass) obj;
- return myClass.primaryAttr1 == primaryAttr1 &&
- myClass.primaryAttr2 == primaryAttr2;
- }
- }
1 在改写equals时要遵守通用约定
1.1 不必改写equals的情况:
1)一个类的每个实例本质上都是惟一的。
2)不关心一个类是否提供了“逻辑相等”的测试功能。
3)超类已经改写的equals,从超类继承的行为对于子类也是合适的。
4)一个类是私有的,或者是包级私有的,并且可以确定它的equals方法永远也不会被调用。
1.2 需要改写Object.equals的情况:
当一个类有自己特有的“逻辑相等”概念,而且父类也没有改写equals以实现期望的行为。通常适合于value class的情形。
改写equals也使得这个类的实例可以被用作map的key或者set的元素,并使map和set表现出预期的行为。
1.3 改写equals要遵守的通用约定(equals方法实现了等价关系):
1)自反性:x.equals(x)一定返回true
2)对称性:x.equals(y)返回true当且仅当y.equals(x)
3)传递性:x.equals(y)且y.equals(z),则x.equals(z)为true
4)一致性:若x.equals(y)返回true,则不改变x,y时多次调用x.equals(y)都返回true
5)对于任意的非空引用值x,x.equals(null)一定返回false。
当重写完equals方法后,应该检查是否满足对称性、传递性、一致性。(自反性、null通常会自行满足)
下面是一个示例:
- public class Point {
- private final int x;
- private final int y;
- public Point(int x, int y) {
- this.x = x;
- this.y = y;
- }
- public boolean equals(Object o) {
- if(!(o instanceof Point))
- return false;
- Point p = (Point)o;
- return p.x == x && p.y == y;
- }
- //...
- }
- import java.awt.Color;
- public class ColorPoint extends Point {
- private final Color color;
- public ColorPoint(int x, int y, Color color) {
- super(x, y);
- this.color = color;
- }
- /* 不满足对称性 */
- public boolean equals(Object o) {
- if(!(o instanceof ColorPoint))
- return false;
- ColorPoint cp = (ColorPoint)o;
- return super.equals(o) && cp.color == color;
- }
- //...
- }
- import java.awt.Color;
- public class Test {
- public static void main(String arg[])
- {
- System.out.println("检验对称性:");
- Point p = new Point(1, 2);
- ColorPoint cp = new ColorPoint(1, 2, Color.RED);
- System.out.println(p.equals(cp));
- System.out.println(cp.equals(p));
- System.out.println("检验传递性:");
- ColorPoint p1 = new ColorPoint(1, 2, Color.RED);
- Point p2 = new Point(1, 2);
- ColorPoint p3 = new ColorPoint(1, 2, Color.BLUE);
- System.out.println(p1.equals(p2));
- System.out.println(p2.equals(p3));
- System.out.println(p1.equals(p3));
- }
- }
此时的输出为:
- 检验对称性:
- true
- false
- 检验传递性:
- false
- true
- false
这表示上述equals方法的写法违背了对称性,但没有违背传递性。之所以不满足对称性,是由于Point的equals方法在接收ColorPoint类型的参数时,会将其当做Point(忽略color属性)进行比较,而ColorPoint的equals方法接收Point类型参数时,判断其不是ColorPoint类型就直接返回false。因此,可以对equals方法作如下修改:
- /* 满足对称性,但牺牲了传递性 */
- public boolean equals(Object o) {
- if(!(o instanceof Point))// 不是Point类型
- return false;
- if(!(o instanceof ColorPoint))// 是Point类型,但不是ColorPoint类型
- return o.equals(this);
- // 是ColorPoint类型
- ColorPoint cp = (ColorPoint)o;
- return super.equals(o) && cp.color == color;
- }
此时的执行输出为:
- 检验对称性:
- true
- true
- 检验传递性:
- true
- true
- false
这表示满足对称性,但牺牲了传递性。由于p1与p2,p2与p3的比较都没有考虑color属性,但是p1与p3比较则考虑了color属性。要在扩展(即继承)一个可实例化的类的同时,既要增加新的属性,同时还要保留equals约定,没有一个简单的方法可以做到。
复合优先于继承。遵照这条原则,这个问题可以有很好的解决方案,不再让ColorPoint继承Point,而是在ColorPoint类中加入一个私有的Point域,以及一个公有的view方法,此方法返回对应的Point对象。
- import java.awt.Color;
- public class ColorPoint {
- private Point point;
- private final Color color;
- public ColorPoint(int x, int y, Color color) {
- point = new Point(x, y);
- this.color = color;
- }
- // 返回ColorPoint的Point视图(view)
- public Point asPoint() {
- return point;
- }
- public boolean equals(Object o) {
- if(!(o instanceof ColorPoint))
- return false;
- ColorPoint cp = (ColorPoint)o;
- return cp.point.equals(point) && cp.color.equals(color);
- }
- // ...
- }
Java平台中有一些类是可实例化类(非抽象类)的子类,且加入了新属性。例如,java.sql.Timestamp继承自java.util.Date,并增加了nanoseconds域,Timestamp的equals违反了对称性,当Timestamp和Date用于集合中时可能出现问题。
注意,可以在一个抽象类的子类中增加新的属性,而不会违反equals约定。因为不可能创建父类的实例,也就不会出现上述问题。
2 不要将equals声明中的Object对象替换为其他类型
- public boolean equals(MyClass obj) {
- ...
- }
上述代码,使用了具体的类MyClass作为参数,这会导致错误。原因在于,这个方法并没有重写(override)Object.equals方法,而是重载(overload)了它。某些情况下,这个具体化的equals方法会提高一些性能,但这样极有可能造成不易察觉的错误。
3 改写equals时同时改写hashCode
3.1 java.lang.Object的规范中,hashCode约定:
1)在一个应用程序执行期间,如果一个对象的equals方法
2)相等对象(equals返回true)必须具有相等的散列码
3)不相等对象(equals返回false)调用hashCode方法,不要求必须产生不同的值;但是产生不同的值有可能提高散列表的性能。
若改写equals时,未同时改写hashCode方法,则可能导致不满足第二条。
3.2 改写hashCode的常用方法:
参考资料:
《Effective Java》(2nd Edition) 第7条、第8条、第14条、第20条、第37条等
该书上貌似目前就推荐使用复合而不是继承,所以最终解决重写equals的传递性的方案是复合。如果各路大神有好的方式麻烦告诉我,在此谢谢了
重写equals时,遵守的规定的更多相关文章
- 为什么重写equals时必须重写hashCode方法?(转发+整理)
为什么重写equals时必须重写hashCode方法? 原文地址:http://www.cnblogs.com/shenliang123/archive/2012/04/16/2452206.html ...
- 讲解:为什么重写equals时必须重写hashCode方法
一 :string类型的==和equals的区别: 结论:"=="是判断两个字符串的内存地址是否相等,equals是比较两个字符串的值是否相等,具体就不做扩展了,有兴趣的同学可以去 ...
- 第10项:重写equals时请遵守通用约定
重写equals方法看起来似乎很简单,但是有许多重写方式会导致错误,而且后果非常严重.最容易避免这类问题的办法就是不覆盖equals方法,在这种情况下,类的每个实例都只能与它自身相等.如果满足了以 ...
- 为什么重写equals时必须重写hashCode方法?
原文地址:http://www.cnblogs.com/shenliang123/archive/2012/04/16/2452206.html 首先我们先来看下String类的源码:可以发现Stri ...
- 第8条:覆盖equals时遵守通用约定
如果不需要覆盖equals方法,那么就无需担心覆盖equals方法导致的错误. 什么时候不需要覆盖equals方法? 1.类的每个实例本质上是唯一的. 例如对于Thread,Object提供的equa ...
- java中为什么重写equals时必须重写hashCode方法?
在上一篇博文Java中equals和==的区别中介绍了Object类的equals方法,并且也介绍了我们可在重写equals方法,本章我们来说一下为什么重写equals方法的时候也要重写hashCod ...
- why在重写equals时还必须重写hashcode方法
首先我们先来看下String类的源码:可以发现String是重写了Object类的equals方法的,并且也重写了hashcode方法 public boolean equals(Object anO ...
- 为什么重写equals时一定要重写hashcode
我们开发时写一个类,默认继承Object类,Object类的equals方法是比较是否指向同一个对象(地址是否相同), Object类 的hashcode方法返回的对象内存地址的值, 一个类只重写了e ...
- Effective Java —— 覆盖equals时遵守通用约定
本文参考 本篇文章参考自<Effective Java>第三版第十条"Obey the general contract when overriding equals" ...
随机推荐
- Unity3D学习笔记(二)Unity的JavaScript基础
Update()每帧调用一次LateUpdate()在Update()后执行Awake()系统执行的第一个方法Start()在Awake()之后,Update()之前FixedUpdate()固定更新 ...
- LeetCode之“树”:Binary Tree Preorder && Inorder && Postorder Traversal
Binary Tree Preorder Traversal 题目链接 题目要求: Given a binary tree, return the preorder traversal of its ...
- 深入理解JNI
深入理解JNI 最近在学习android底层的一些东西,看了一些大神的博客,整体上有了一点把握,也产生了很多疑惑,于是再次把邓大神的深入系列翻出来仔细看看,下面主要是一些阅读笔记. JNI概述 JNI ...
- 面试之路(27)-链表中倒数第K个结点
代码的鲁棒性: 所谓的鲁棒性是指能够判断输入是否合乎规范,能对不和规范的程序进行处理. 容错性是鲁棒性的一个重要体现. 防御性编程有助于提高鲁棒性. 切入正题,我可不是标题党: 链表倒数第k个节点 列 ...
- 如何使用ZOL一键安装器下载中关村在线的源安装包
如何使用ZOL一键安装器下载中关村在线的源安装包 马根峰 (广东联合电子服务股份有限公司, 广州 510300) 摘要 中关村在线最近开始推出ZOL一键安装器,用户直 ...
- 服务器:SATA、PATA及IDE的比较
SATA SATA全称是Serial Advanced Technology Attachment(串行高级技术附件,一种基于行业标准的串行硬件驱动器接口),是由Intel.IBM.Dell.APT. ...
- 基于hashchange导航管理
想在五一放假的时候写出来,由于放假有点兴奋,心早就跑了,不废话了. 说一下基于hashchange导航管理: 浏览器的历史记录导航是用户非常常用的功能,除了点击前进后退按钮外,Window上的hist ...
- java中Scanner类nextLine()和next()的区别和使用方法
转载:http://blog.csdn.net/zhiyuan_ma/article/details/51592730 在实现字符窗口的输入时,很多人更喜欢选择使用扫描器Scanner,它操作起来比较 ...
- C语言的产生
一:C语言的产生 C语言是1972年由美国的Dennis Ritchie设计发明的,并首次在UNIX操作系统的DEC PDP-11计算机上使用的. 它由早期的编程语言BCPL 演变而来,随着微型计算 ...
- 简单而强大的bitset
简单而强大的bitset 介绍 有些程序需要处理二进制有序集,标准库提供了bitset 类型,事实上,bitset 是一个二进制容器,容器中每一个元素都是一位二进制码,或为 0,或为 1. 基础 bi ...