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

          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对象替换为其他的类型


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值