1. 什么时候需要重写Object.equals方法

如果类具有自己特有的“逻辑相等”概念(不同于对象等同的概念),而且超类还没有覆盖equals以实现期望的行为,这时我们就需要覆盖equals方法。

这通常属于“值类(value class)”的情形。值类仅仅是一个表示值的类,例如Integer或Date。程序员在利用equals方法来比较值对象的引用时,希望知道它们在逻辑上是否相等,而不是想了解它们是否指向同一个对象。

有一种“值类”不需要覆盖equals方法,即用实例受控确保“每个值至多只有一个对象”的类,例如枚举类型。对于这样的类,逻辑相同与对象等同是一回事。

2.equals的通用约定

· 自反性reflexive。对于任何非null的引用值x, x.equals(x)必须返回true;

· 对称性symmetric。对于任何非null的引用值x和y, 当且仅当y.equals(x)返回true时,x.equals(y)必须返回true;

· 传递性transitive。对于任何非null的引用值x, y和z,如果x.equals(y)返回true, 并且 y.equals(z) 返回true,那么x.equals(z)也必须返回true;

· 一致性consistent。对于任何非null的引用值x和y,只要equals的比较操作在对象中所用的信息没有被修改,多次调用x.equals(y)就会一致地返回true,或者一致返回false;因此,equals方法里面不应该依赖任何不可靠的资源

· 对于任何非null的引用值x, x.equals(null)必须返回false

3.一些不好的示例

1)违反对称性。

定义如下一个类。

 class CaseInsensitiveString {

     private String insensitiveString;

     public CaseInsensitiveString(String string) {
this.insensitiveString = string;
} public String getString() {
return this.insensitiveString;
} @Override
public boolean equals(Object object) {
if (null == this.insensitiveString) {
return false;
} if (object instanceof CaseInsensitiveString) {
return this.insensitiveString
.equalsIgnoreCase(((CaseInsensitiveString) object)
.getString());
} if (object instanceof String) {
return this.insensitiveString.equals((String) object);
} return false;
}
}

该类的equals方法,首先违反了对称性。

 public class App {

     public static void main(String[] args) {
CaseInsensitiveString cis = new CaseInsensitiveString("whatever");
String string = "whatever";
System.out.println(cis.equals(string));
System.out.println(string.equals(cis));
} }

返回结果将是true和false。

2)考虑继承的场景。

假设下面有一个圆形的类,只要半径相同,就认为两个圆逻辑上等同。

 class Circle {
private int radius; public Circle(int radius) {
this.radius = radius;
} public int getRadius() {
return this.radius;
} @Override
public boolean equals(Object object) {
if (object instanceof Circle) {
return this.radius == ((Circle) object).getRadius();
} return false;
}
}

假如想要扩展这个类,为其加上一个颜色color的属性,那么其子类的equals方法要怎么写呢?

 class ColorCircle extends Circle {

     public static enum COLOR {
RED, WHITE, BLUE, GREEN, YELLOW
}; private COLOR color; public COLOR getColor() {
return this.color;
} public ColorCircle(int radius, COLOR color) {
super(radius);
this.color = color;
// TODO Auto-generated constructor stub
}
}

如果不覆盖equals方法,使用其父类的方法,那么,新添加的属性就会被忽略。虽然这样做不违反约定,但这样显然是无法接受的,否则为什么要新增一个属性?

那么,为了突出新增加的属性,提供如下的equals方法。

 @Override
public boolean equals(Object object) {
if (!(object instanceof ColorCircle)) {
return false;
} return super.equals(object)
&& ((ColorCircle) object).getColor() == this.color;
}

这样做,违反了对称性。下面实例化了两个对象,一个是以Circle,另一个是带颜色的ColorCircle,很明显,打印出来的结果一个是true,一个是false.

 public static void main(String[] args) {
Circle circle = new Circle(1);
ColorCircle colorCircle = new ColorCircle(1, COLOR.BLUE);
System.out.println(circle.equals(colorCircle));
System.out.println(colorCircle.equals(circle));
}

那么也许你会说,这种情况,那就判断其是子类还是父类,采用不同的策略进行比较。

 @Override
public boolean equals(Object object) {
if(!(object instanceof Circle)) {
return false;
} if (!(object instanceof ColorCircle)) {
return super.equals(object);
}
else {
return super.equals(object)
&& ((ColorCircle) object).getColor() == this.color;
}
}

这样是解决了对称性的问题了,但又带来了另一个问题,传递性。

     public static void main(String[] args) {
ColorCircle colorCircle = new ColorCircle(1, COLOR.BLUE);
Circle circle = new Circle(1);
ColorCircle anotherColorCircle = new ColorCircle(1, COLOR.RED);
System.out.println(colorCircle.equals(circle));
System.out.println(circle.equals(colorCircle));
System.out.println(colorCircle.equals(anotherColorCircle));
}

如上,colorCircle和circle等同,circle与anotherColorCircle等同,而很明显,colorCircle和anotherColorCircle是不等同的。

事实上,这是面向对象语言中,关于等价关系的一个基本问题。我们无法在扩展可实例化的类的同时,既增加新的值组件,同时又保留equals约定,除非愿意放弃面向对象的抽象带来的优势。

注意,这里说的是可实例化的类的扩展,对于抽象类不影响。

当然,你也可以用getClass测试代替instanceof测试,可以扩展可实例化的类和增加新的值组件,同时保留equals约定:

     @Override
public boolean equals(Object object) {
if (null == object || object.getClass() != getClass()) {
return false;
} ColorCircle colorCircle = (ColorCircle) object;
return this.getRadius() == colorCircle.getRadius()
&& this.color == colorCircle.getColor();
}

但是违背了里氏替换原则:任何基类出现的地方都可以无差别地使用子类替换。很明显,如果两个Circle等同,此时将其中一个替换为ColorCircle类,则不再等同。因此,子类应尽量少重写父类的方法。

总结一下,父类和子类的这种equals方法的重写问题的原因,首先,是因为父类是一个可实例化的类。那么就可能出现父类对象与子类对象比较的场景。所以对于抽象类来讲,并不影响,因为其无法实例化。

其次,是因为子类增加了一些标识身份的属性,必须在判断等同时使用。那么当父类与子类两个对象进行比较时,就会出现这种冲突,就需要思考,这个属性在比较的时候到底要不要使用。

因此,当【父类可实例化】且【子类新增属性必须参与到equals比较】的时候,equals方法总是会违反某些原则。

一个可以采用的方法是,组合优先于继承。在这里就不展开讨论了。

重写equals方法的约定的更多相关文章

  1. Effective Java 第三版——10. 重写equals方法时遵守通用约定

    Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...

  2. Java中Set的contains()方法——hashCode与equals方法的约定及重写原则

    转自:http://blog.csdn.net/renfufei/article/details/14163329 翻译人员: 铁锚 翻译时间: 2013年11月5日 原文链接: Java hashC ...

  3. Effective Java 第三版——11. 重写equals方法时同时也要重写hashcode方法

    Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...

  4. 重写equals方法(未完)

    equals方法是我们日常编程中很常见的方法,Object中对这个方法的解释如下: boolean equals(Object obj) 指示其他某个对象是否与此对象“相等”. 查看该方法的底层代码如 ...

  5. 为什么重写equals方法时,要求必须重写hashCode方法?

    1 equals方法 Object类中默认的实现方式是  :   return this == obj  .那就是说,只有this 和 obj引用同一个对象,才会返回true. 而我们往往需要用equ ...

  6. 为什么重写equals方法,还必须要重写hashcode方法

    一.equals方法和hashcode的关系 根据Object.hashCode的通用约定: 如果两个对象相同(equals方法返回true),那么hashcode也相等.(图1) 如果两个对象的ha ...

  7. java重写equals方法

    @Override public int hashCode() { return task.getId(); } @Override public boolean equals(Object obj) ...

  8. 重写equals()方法时,需要同时重写hashCode()方法

    package com.wangzhu.map; import java.util.HashMap; /** * hashCode方法的主要作用是为了配合基于散列的集合一起正常运行,<br/&g ...

  9. 为什么重写equals方法还要重写hashcode方法?

    我们都知道Java语言是完全面向对象的,在java中,所有的对象都是继承于Object类.Ojbect类中有两个方法equals.hashCode,这两个方法都是用来比较两个对象是否相等的. 在未重写 ...

随机推荐

  1. BZOJ_1611_[Usaco2008_Feb]_Meteor_Shower流星雨_(bfs)

    描述 http://www.lydsy.com/JudgeOnline/problem.php?id=1611 网格图起始位置(0,0),不同时间会有流星落下,导致之后的时间里,该点以及周围四个点都不 ...

  2. Nagios Core/Icinga 基于栈的缓冲区溢出漏洞

    漏洞名称: Nagios Core/Icinga 基于栈的缓冲区溢出漏洞 CNNVD编号: CNNVD-201402-484 发布时间: 2014-03-03 更新时间: 2014-03-03 危害等 ...

  3. 用Remastersys定制自己的Ubuntu安装光盘

    这两天因为要做Ubuntu的平台移植,一直在给自己电脑上的Ubuntu系统装各种软件,其间几次将内核破坏,导致不得不重装系统,经过几次痛苦的等待,痛定思痛,决定试一试能不能将自己的Ubuntu系统定制 ...

  4. 做一个自己的最小Linux系统

     一.前言 Linux操作系统至1991.10.5号诞生以来,就源其开源性和自由性得到了很多技术大牛的青睐,每个Linux爱好者都为其贡献了自己的一份力,不管是在Linux内核还是开源软件等方面,都为 ...

  5. 如何用udev for asm in oracle linux 6

    大部分在网上可以找到的文档都是在RHEL5或者OEL5中设置udev,udev对于Linux而言最大的作用是防止操作系统重新启动以后,作为ASM磁盘使用的盘符发生变化.比如说Tim Hall的文章:U ...

  6. 【转】Xcode常用快捷键与技巧分享

    原文网址:http://www.jianshu.com/p/039954b0cbe0 工欲善其事必先利其器. 虽然Xcode编写objective-c or swift很完美, 但了解其工具的常用快捷 ...

  7. CSS+DIV之强化background属性

    1.背景颜色属性(background-color),设定背景颜色=html中bgcolor属性.我来写一个红色背景的body,(也可以定义某个特定区域) 示例写法:body {} 2.背景图片属性( ...

  8. BrnMall多店版网上商城正式发布

    前些日子一直忙于多店版网上商城系统BrnMall的开发,工作比较多,所以博客断了.这几天项目完成了,时间比较自由,所以把这段时间总结的一些关于单店版BrnShop和多店版BrnMall区别写下来,希望 ...

  9. 在内部架设NuGet服务器

    在公司内部有很多基础框架或者基础组件,甚至对于使用SOA架构的公司来说,会有大量的业务组件的契约程序集,对于这些框架或组件的引用管理有的人使用源代码管理工具,但是NuGet相比源代码管理工具更方便: ...

  10. SAE网站搭建(2)

    通过djangao admin界面无法添加汉字(unicode)的解决办法. 根源:mysql服务器的默认设置不支持unicode(utf8) 解决办法: 将/etc/mysql/my.cnf中utf ...