《Effective Java》读书笔记——覆盖equals时请遵守通用约定

时间:2021-10-07 16:15:40
          equals()方法是Object类的方法,而Object是所有类的*父类,所有的类都会拥有Object的方法,也会拥有equals()方法。在Object类中,equals()方法的实现特别简单,比较的两个实例为同一个实例则相等。

 public boolean equals(Object obj) {
return (this == obj);
}

一.什么时候不需要覆盖equals()方法?

1.类的每个实例本质上都是唯一的。例如枚举类型,或者Thread类,他们的每一个实例都是唯一的。

2.不关心是否提供了“逻辑相等”的测试功能。

3.超类已经覆盖了equals()方法,从超类继承过来的行为对于子类也是适合的。

4.类是私有的或者包级私有的,可以确定他的equals()方法永远不会被调用。


二.什么时候需要覆盖equals()方法?

       如果类具有自己特定的“逻辑相等”概念。而且超类还没有覆盖equals()方法以实现期望的行为,这时我们就应该需要覆盖equals()方法。通常,需要覆盖equals()方法的类属于“值类”,值类是仅仅表示一个值的类,例如Integer类,程序员使用这些类的equals()方法比较对象的引用时,是希望知道它们在逻辑之上是否相等,而不是想知道它们是否指向同一个对象。

三.覆盖equals()方法的时候需要遵守的约定

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

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

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

4.一致性:对于任何非null的引用值x和y,只要equals()的比较操作在对象中所用的信息没有被修改,那么多次调用equals()方法必须返回相同的结果。

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

四.常见的违背equals约定的例子

1.对称性:

public final class CaseInsensitiveString {

private final String s;

public CaseInsensitiveString(String s)
{
if(s == null)
throw new NullPointerException();
this.s = s;
}
@Override
public boolean equals(Object obj) {
if(obj instanceof CaseInsensitiveString)
return s.equalsIgnoreCase(((CaseInsensitiveString) obj).s);
if(obj instanceof String)
return s.equalsIgnoreCase((String) obj);
return false;
}

public static void main(String[] args) {
CaseInsensitiveString c = new CaseInsensitiveString("Person");
String s = "person";
System.out.println(c.equals(s));
System.out.println(s.equals(c));
}

}

在这个例子中,CaseInsensitiveString的equals()方法的目的是与普通的字符串进行操作,equals()方法知道普通的字符串,所以可以进行比较,但是String类中却不知道CaseInsensitiveString类,所以比较结果是false,这就违反了对称性,所以上面的equals()方法中不能有与String相互操作的代码部分。

3.传递型:

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 obj) {
if(!(obj instanceof Point))
return false;
Point p = (Point) obj;
return p.x == x&&p.y == y;
}

}

这是一个普通的点的类,接着可能他还有一个子类,就是为这个点着色:

public class ColorPoint extends Point {
private final Color color;

public ColorPoint(int x, int y ,Color color) {
super(x, y);
this.color = color;
}
}

如果这个类不覆盖父类的equals()方法,这样做不会违背equals的约定,但是颜色却会被忽略,所以必须覆盖equals()方法。
public boolean equals(Object obj) {
if(!(obj instanceof ColorPoint))
return false;
return super.equals(obj)&&((ColorPoint) obj).color == color;
}

上面的equals()方法在比较普通点和有色点的时候,可能会出现不同的结果,违背对称性。所以这种方式不行。
<pre name="code" class="java">public boolean equals(Object obj) {

if(!(obj instanceof Point))
return false;
if(!(obj instanceof ColorPoint))
return obj.equals(this);
return super.equals(obj)&&((ColorPoint) obj).color == color;

}
 
 

上面的equals()方法满足了对称性,但是却违背的传递性,比如有三个点,分别是(1,3,Color.RED)、(1,3)、(1,3,Color.BLUE),第一个点与第二个点比较返回true,第二个点与第三个点比较也返回true,但是第一个点与第三个点之间比较会返回false,这就违背了传递性。这是因为我们无法在扩展可实例化的类的同时,既增加新的组件,同时又保留equals的约定。解决的办法是我们采用复合优先于继承,我们不让ColorPoint类扩展Point,而是在ColorPint类中加入一个私有的Point域。

public boolean equals(Object obj) {
if(!(obj instanceof ColorPoint))
return obj.equals(this);
ColorPoint cp = (ColorPoint)obj;
return cp.point.equals(obj)&&cp.color == color;

}

五.实现高质量的equals()方法的诀窍

1.使用==操作符检查“参数是否为这个对象的引用”,如果是则返回true。这只是一种性能优化的手段。

2.使用instanceof操作符检查“参数是否为正确的类型”,如果不是,则返回false。

3.把参数转换成正确的类型。

4.对于该类中的每个关键域,检查参数中的域是否与该对象中对应的域相匹配。

5.检查该类是否满足对称性、传递性、一致性。

6.其他注意事项

1.覆盖equals()方法的时候总是要覆盖hashCode()方法。

2.不要让equals()方法过于智能。

3.不要将equals()声明的中的Object对象替换为其他的类型