在覆盖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. TCP系列28—窗口管理&流控—2、延迟ACK(Delayed Acknowledgments)

    一.简介 之前的内容中我们多次提到延迟ACK(Delayed Ack),延迟ACK是在RFC1122协议中定义的,协议指出,一个TCP实现应该实现延迟ACK,但是ACK不能被过度延迟,协议给出延迟AC ...

  2. 大型网站架构演化(六)——使用反向代理和CDN加速网站响应

    随着网站业务不断发展,用户规模越来越大,由于中国复杂的网络环境,不同地区的用户访问网站时,速度差别也极大.有研究表明,网站访问延迟和用户流失率正相关,网站访问越慢,用户越容易失去耐心而离开.为了提供更 ...

  3. Css入门课程 Css基础

    html css javascript三者关系 html是网页内容的载体 css是网页内容的表现,外观控制 javascript是网页逻辑处理和行为控制 css相对于html标签属性的优势 css简化 ...

  4. 关于c++中public & private方法调用问题

    class IDNoIdentifier { public: IDNoIdentifier(); ~IDNoIdentifier(); typedef vector<cv::Rect> C ...

  5. 【EF】EF实现大批量数据库插入操作

    通过参考http://www.itnose.net/news/171/6306259里面介绍的方法,我们可以在程序的解决方案的引用中选择NuGet管理包中添加  该扩展方法提供BulkInsert() ...

  6. 当要将其他类型转成String类型时候 看String的方法

    当要将其他类型转成String类型时候 看String的方法进行转换

  7. 【bzoj2100】[Usaco2010 Dec]Apple Delivery 最短路

    题目描述 Bessie has two crisp red apples to deliver to two of her friends in the herd. Of course, she tr ...

  8. hadoop 使用Avro排序

    在上例中,使用Avro框架求出数据的最大值,本例使用Avro对数据排序,输入依然是之前的样本,输出使用文本(也可以输出Avro格式). 1.在Avro的Schema中直接设置排序方向. dataRec ...

  9. spring任务执行器与任务调度器(TaskExecutor And TaskScheduler)

    对于多线程及周期性调度相关的操作,spring框架提供了TaskExecutor和TaskScheduler接口为异步执行和任务调度.并提供了相关实现类给开发者使用.(只记录采用注解的使用形式,对于X ...

  10. Ubuntu安装teamviewer注意事项。

    Ubuntu安装teamviewer注意事项. 首先通过浏览器到官方下载ubuntu对应teamviewer的安装包 但是通过dpkg –i安装之后发现安装过程出问题,安装好的包打开之后也闪退. 这个 ...