您的位置:首页 > 其它

Item 8 覆盖equals时请遵守通用约定

2015-03-24 20:53 246 查看
在覆盖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));
}
}


[align=left]-------------------------[/align]

[align=left]true false[/align]
[align=left] [/align]
[align=left]上述代码的问题,要通过写测试来发现。写测试的时候,按照对称性规则来写。对称性规则就是 x.equals(y)为true, y.equals(x)也要为true。[/align]
[align=left]如果,没有按照规则来写单元测试,那么,是不容易发现上述代码存在的问题的。关键就是,按照规则,实现了代码之后,要写单元测试。[/align]
[align=left] [/align]
[align=left]传递性[/align]
[align=left] [/align]

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));
}
}


[align=left]-----------------------------------------------------[/align]

[align=left]结果:[/align]
[align=left]true false[/align]
[align=left]false true false[/align]

[align=left] [/align]

[align=left]同理,使用传递性规则,写个测试,就可以发现问题。[/align]
[align=left] [/align]
[align=left]解决办法:[/align]
[align=left]使用复合,避免使用继承。也就是,在实现ColorPoint时,不继承Point,而在ColorPoint中放一个Point类型的成员。然后,所有ColorPoint都有Point类成员,Color成员,比较两个ColorPoint就是比较这两个成员的值。而不是跟上面那样,因为使用了继承,ColorPoint可以和Point进行比较。[/align]
[align=left] [/align]

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();
}
}


[align=left]一致性[/align]
[align=left] [/align]
[align=left]“ 如果两个对象相等,它们就必须始终保持相等,除非它们中有一个对象(或者两个都)被修改了。换句话说,可变对象在不同的时候可以与不同的对象相等,而不可变对象则不会这样。”[/align]
[align=left] [/align]
[align=left]“无论类是否是不可变的,都不要使equals方法依赖于不可靠的资源。如果违反了这条禁令,要想满足一致性的要求就十分困难了。”比如,比较的时候,依赖解析到的IP地址:[/align]

@Override

public boolean equals(Object obj) {

if (!(obj instanceof InetAddress)) {

return false;

}

return Arrays.equals(this.ipaddress, ((InetAddress) obj).ipaddress);

}

[align=left]在比较两个URL是否相等时,java.net.URL使用了这样的依据,两个URL对应的IP地址是否是相等的,如果相等,则它们两个是同一个URL。它这个实现,依赖了不可靠的资源,IP地址。IP地址,随着时间的推移,当时相等的两个URL,会不相等,因为,它们在比较的时候,还要解析IP地址。[/align]
[align=left] [/align]

非空性

是指比较的两个对象,不能为空。但是,在重写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约定。如果想过度地去寻求各种等价关系,则很容易陷入麻烦之中。

[align=left] [/align]
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: