在覆盖equals方法的时候,你必须要遵守它的通用约定,不遵守,写出来的方法,会出现逻辑错误。下面是约定的内容:

 
equals方法实现了等价关系:
 
  • 自反性。对于任何非null的引用值,x.equals(x)必须返回true。
  • 对称性。对于任何非null的引用值x和y,当且仅当y.equals(x)返回true时,x.equals(y)必须返回true。
  • 传递性。对于任何非null的引用值x,y和z,如果x.equals(y)返回true,并且y.equals(z)也返回true,那么x.equals(z)也必须返回true。
  • 一致性。对于任何非null的引用值x和y,只要equals的比较操作在对象中所用的信息没有被修改,多次调用x.equals(y)就会一致地返回true,或者一致地返回false。
  • 对于任何非null的引用值x,x.equals(null)必须返回false。

对称性

public final class CaseInsensitiveString {
private final String s ; public CaseInsensitiveString(String s) {
if (s == null)
throw new NullPointerException();
this. s = s;
} // Broken - violates symmetry!
@Override
public boolean equals(Object o) {
if (o instanceof CaseInsensitiveString)
return s.equalsIgnoreCase(((CaseInsensitiveString) o). s);
if (o instanceof String) // One-way interoperability!
return s.equalsIgnoreCase((String) o);
return false;
} // This version is correct.
// @Override public boolean equals(Object o) {
// return o instanceof CaseInsensitiveString &&
// ((CaseInsensitiveString) o).s.equalsIgnoreCase(s);
// } public static void main(String[] args) {
CaseInsensitiveString cis = new CaseInsensitiveString("Polish" );
String s = "polish";
System.out.println(cis.equals(s) + " " + s.equals(cis));
}
}
-------------------------
true  false
 
上述代码的问题,要通过写测试来发现。写测试的时候,按照对称性规则来写。对称性规则就是  x.equals(y)为true, y.equals(x)也要为true。
如果,没有按照规则来写单元测试,那么,是不容易发现上述代码存在的问题的。关键就是,按照规则,实现了代码之后,要写单元测试。
 
传递性
 
public class ColorPoint extends Point {
private final Color color ; public ColorPoint( int x, int y, Color color) {
super(x, y);
this.color = color;
} // Broken - violates symmetry!
@Override
public boolean equals(Object o) {
if (!(o instanceof ColorPoint))
return false ;
return super .equals(o) && ((ColorPoint) o).color == color;
} // Broken - violates transitivity!
// @Override public boolean equals(Object o) {
// if (!(o instanceof Point))
// return false;
//
// // If o is a normal Point, do a color-blind comparison
// if (!(o instanceof ColorPoint))
// return o.equals(this);
//
// // o is a ColorPoint; do a full comparison
// return super.equals(o) && ((ColorPoint)o).color == color;
// } public static void main(String[] args) {
// First equals function violates symmetry
Point p = new Point(1, 2);
ColorPoint cp = new ColorPoint(1, 2, Color.RED);
System. out.println(p.equals(cp) + " " + cp.equals(p)); // Second equals function violates transitivity
ColorPoint p1 = new ColorPoint(1, 2, Color.RED);
Point p2 = new Point(1, 2);
ColorPoint p3 = new ColorPoint(1, 2, Color.BLUE);
System. out.printf("%s %s %s%n" , p1.equals(p2), p2.equals(p3),
p1.equals(p3));
}
}
-----------------------------------------------------
结果:
true false
false true false
 
同理,使用传递性规则,写个测试,就可以发现问题。
 
解决办法:
使用复合,避免使用继承。也就是,在实现ColorPoint时,不继承Point,而在ColorPoint中放一个Point类型的成员。然后,所有ColorPoint都有Point类成员,Color成员,比较两个ColorPoint就是比较这两个成员的值。而不是跟上面那样,因为使用了继承,ColorPoint可以和Point进行比较。
 
public class Point {
private final int x;
private final int y; public Point(int x, int y) {
this.x = x;
this.y = y;
} @Override
public boolean equals(Object o) {
if (!(o instanceof Point))
return false ;
Point p = (Point) o;
return p.x == x && p.y == y;
} // See Item 9
@Override
public int hashCode() {
return 31 * x + y ;
}
} public enum Color {
RED, ORANGE , YELLOW, GREEN, BLUE, INDIGO , VIOLET
} public class ColorPoint {
private final Point point ;
private final Color color ; public ColorPoint(int x, int y, Color color) {
if (color == null)
throw new NullPointerException();
point = new Point(x, y);
this.color = color ;
} /**
* Returns the point -view of this color point.
*/
public Point asPoint() {
return point ;
} @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);
} @Override
public int hashCode() {
return point .hashCode() * 33 + color.hashCode();
}
}
一致性
 
“ 如果两个对象相等,它们就必须始终保持相等,除非它们中有一个对象(或者两个都)被修改了。换句话说,可变对象在不同的时候可以与不同的对象相等,而不可变对象则不会这样。”
 
“无论类是否是不可变的,都不要使equals方法依赖于不可靠的资源。如果违反了这条禁令,要想满足一致性的要求就十分困难了。”比如,比较的时候,依赖解析到的IP地址:
 
  1. @Override
  2. public boolean equals(Object obj) {
  3. if (!(obj instanceof InetAddress)) {
  4. return false;
  5. }
  6. return Arrays.equals(this.ipaddress, ((InetAddress) obj).ipaddress);
  7. }
在比较两个URL是否相等时,java.net.URL使用了这样的依据,两个URL对应的IP地址是否是相等的,如果相等,则它们两个是同一个URL。它这个实现,依赖了不可靠的资源,IP地址。IP地址,随着时间的推移,当时相等的两个URL,会不相等,因为,它们在比较的时候,还要解析IP地址。
 
非空性
 
是指比较的两个对象,不能为空。但是,在重写equals方法时,无需使用 if( o == null ) return false。因为,测试两个对象是否相等时,可以直接使用instanceof运算符:
	*
@Override
* public boolean equals(Object obj) {
* if (!(obj instanceof InetAddress)) {
* return false;
* }
* return Arrays.equals(this.ipaddress, ((InetAddress) obj).ipaddress);
* }  
instanceof运算符,包含了null的检测,也就是说,obj如果为空,那么 obj instanceof InetAddress是false的,这样就不会进行具体的比较,直接返回false.
 
来自《effective java》中的诀窍,实现高质量的equals的诀窍:
 
1.使用 == 操作符检查 “参数是否为这个对象的引用”。如果是,则返回true。这只不过是一种性能优化,如果比较操作有可能很昂贵,就值得这么做。使用==操作符,将当前独享的引用与参数进行比较。
 
 
2.使用instanceof操作符检查”参数是否为正确的类型”。如果不是,则返回false。一般说来,所谓“正确的类型”是指equals方法所在的那个类。有些情况下,是指该类所实现的某个接口。
 
3.把equals方法传入的参数转换成正确的类型。
 
4.对于该类中的每个“关键(significant)”域,检查参数中的域是否与该对象中对应的域相匹配。如果这些测试全部成功,则返回true;否则返回false。
 
5.对于float,double类型的关键域,使用Float.compare,Double.compare来进行比较,而不使用==,这是因为存在Float.NaN,-0.0f以及类似的double常量。
 
6.对于关键域的类型是非float,double类型的基本类型,使用==进行比较。
--------------------------------------------------------------------------------------
注意:
 
1.覆盖equals时总要覆盖hashCode方法,这是为了让该类可以在使用了散列的集合中使用,比如Map.
 
2.不要将equals声明中的Object对象替换为其它的类型。因为,我们是在覆盖java.lang.object的equals方法,而不是重载。覆盖表示,某个方法,在子类表现的行为是不同的;而重载,则是提供了两种行为,一个方法。
 
3.不要企图让equals方法过于智能。如果只是简单地测试域中的值是否相等,则不难做到准守equals约定。如果想过度地去寻求各种等价关系,则很容易陷入麻烦之中。
 

Item 8 覆盖equals时请遵守通用约定的更多相关文章

  1. 第8条:覆盖equals时请遵守通用约定

    第8条:覆盖equals时请遵守通用约定 引言:尽管Object是一个具体类,但是设计它主要是为了拓展.它所有的非final方法(equals.hashCode.toString.clone和fina ...

  2. 第八条:覆盖equals时请遵守通用约定

    ==是物理相等 equals是逻辑相等 因为每个类的实例对象本质上都是唯一的 ,利用物理相等(==)是指一个实例只能相等于它自己. 利用逻辑相等是(equals)指 一个实例是否和另一个实例的某些关键 ...

  3. 覆盖equals时请遵守通用约定

    Object类中非final修饰的方法有equals().hashCode().toString().finalize().clone()1.equals()方法不需要被覆盖的情况:1)实例化的对象只 ...

  4. 【Effective Java】4、覆盖equals时请遵守通用约定

    package cn.xf.cp.ch02.item8.transitivity; public class Point { private final int x; private final in ...

  5. 第10项:重写equals时请遵守通用约定

      重写equals方法看起来似乎很简单,但是有许多重写方式会导致错误,而且后果非常严重.最容易避免这类问题的办法就是不覆盖equals方法,在这种情况下,类的每个实例都只能与它自身相等.如果满足了以 ...

  6. 覆盖equals方法时请遵守通用约定

    覆盖equals方法时请遵守通用约定   覆盖equals方法看起来很简单,但是有许多覆盖方式会导致错误,并且后果很严重.最容易避免这种类问题的方法就是不覆盖equals方法,在这种情况下,类的每个实 ...

  7. EffectiveJava(8)覆盖equals是要遵守的约定

    覆盖equals是要遵守的约定 1.覆盖种类: -类的每个1实例本质上都是唯一的 -不关心类是否提供了"逻辑相等"的测试功能(Random测试是否能随机相同数字) -超类已经覆盖了 ...

  8. Item 9 覆盖equals时总要覆盖hashCode

    为什么覆盖equals时,总要覆盖hashCode?   原因是,根据Object规范: 如果两个对象根据equals(Object)方法比较是相等的,那么调用这两个对象中任意一个对象的hashCod ...

  9. 重写equals时,遵守的规定

      0 正确的equals方法 public class MyClass { // 主要属性1 private int primaryAttr1; // 主要属性2 private int prima ...

随机推荐

  1. Java学习个人备忘录之数组工具类

    下面主要讲解一个针对数组操作的工具类. a.java -- 工具类文件 //按理来说要先编译本文件, 然后再编译主函数 class ArrayTool { /* 获取整型数组的最大值 */ publi ...

  2. 《学习OpenCV》课后习题解答3

    题目:(P104) 创建一个大小为100*100的三通道RGB图像.将它的元素全部置0.使用指针算法以(20,5)与(40,20)为项点绘制一个绿色平面. 解答: #include "cv. ...

  3. 【week2】 四则运算改进

    四则运算满足简单加减乘除,以及包含括号的复杂四则运算. 代码描述: 1.采用random随机数产生要参与计算的数字,以及运算符号 2.采用Scanner获取控制台输入的结果,与计算出来的结果进行比对, ...

  4. 【Redis】- 缓存击穿

    什么是缓存击穿 在谈论缓存击穿之前,我们先来回忆下从缓存中加载数据的逻辑,如下图所示 因此,如果黑客每次故意查询一个在缓存内必然不存在的数据,导致每次请求都要去存储层去查询,这样缓存就失去了意义.如果 ...

  5. ORA-00933 SQL命令未正确结束 INSERT INTO ... SELECT

    最近在修改数据库存储过程时,出现了一个ORA-00933错误, 执行的是 INSERT INTO...SELECT 语句,具体语句如下: INSERT INTO BASP_DX.QLR@GT(BDCD ...

  6. MAC搭建 PHP 环境

    安装homebrew homebrew是mac下非常好用的包管理器,会自动安装相关的依赖包,将你从繁琐的软件依赖安装中解放出来. 安装homebrew也非常简单,只要在终端中输入11: 1 ruby ...

  7. hdu5575 Discover Water Tank

    题意: 给出个水箱,水箱两侧有无限高的隔板,水箱内有整数高度的隔板将水箱分成n-1份,现在给出m个限制,每个限制表示某个位置的某个高度有水或没水,问最多能同时满足多少个限制.n,m<=2*10^ ...

  8. Django 2.0 学习(10):Django 定制化

    定制化admin表单 通过使用admin.site.register(Question)注册Question模型,Django可以构造默认的表单.通常,可以通过对象的注册机制来告诉Django我们想要 ...

  9. 使用for循环遍历数组元素

    循环可以将代码块执行指定的次数.如果您希望一遍又一遍地运行相同的代码,并且每次的值都不同,那么使用循环是很方便的.迭代语句又叫循环语句. JavaScript 支持不同类型的循环: for - 循环代 ...

  10. BZOJ1195 HNOI2006最短母串(状压dp)

    按照子串出现的先后考虑.令f[i][j]为已经出现的字符串集合为i,最后一个出现的字符串为j时的最短串长,预处理一下任意两个串的最长重叠长度,转移显然.有点麻烦的是字典序,强行增加代码难度. 另一个比 ...