Effective Java -- 对于所有对象都通用的方法 -- 覆盖equals时请遵守通用约定

第三章 对于所有对象都通用的方法

本章将讲述何时以及如何覆盖Object类中的非final方法(equals、hashCode、toString、clone和finalize)。

第十条 覆盖equals时请遵守通用约定

覆盖equals方法看起来很简单,但是会许多覆盖方式会导致错误。

一 有时并不需要覆盖

有时,我们期望类的每个实例都只与它自身相等:

1 类的每个实例本质上都是唯一的

对于代表活动实体而不是值的类(如Thread)正是如此。

2 类没有必要提供“逻辑相等”的测试功能

设计者并不认为客户需要这样的功能(如Pattern)

3 父类已经覆盖了equals

并且父类的行为对于这个类也是合适的(如Set从Abstract继承equals实现)

4 类是私有的或包级私有的

这个类的equals方法永远不会被调用。
但是如果非常想规避风险,也可以选择覆盖equals方法,以确保它不会被意外调用:

	@Override
	public boolean equals(Object o) {
		throw new AssertionError();
	}
5 单例模式的类

对于singleton而言,逻辑相同和对象等同是一回事。

二 通用约定

当类具有自己特有的“逻辑相等”概念时,可以选择覆盖equals方法,这通常属于“值类”的情形(如Integer或者String)。

在覆盖equals方法时,必须遵守它的通用约定:

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

即对象必须等于其本身。

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

任何两个对象对于“它们是否相等”的问题都必须保持一致。

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

如果第一个对象等于第二个对象,而第二个对象又等于第三个对象,则第一个对象一定等于第三个对象。

4 一致性(consistent)
 对于任何非 null 的引用值 x 和 y,
 只要 equals 的比较操作在对象中所用的信息没有被修改,
 多次调用应该一致的返回相同结果

如果两个对象相等,它们就必须始终保持相等,除非它们中有一个对象被修改了。

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

所有对象都不能等于 null。

@Override
public boolean equals(Object o) {
	if (! (o instanceof MyType)){
		return false;
	}
	MyType mt = (MyType) o;
	...
}

三 诀窍

1 使用 ==

使用 == 操作符检查“参数是否为这个对象的引用”。如果是,则返回 true。

2 使用 instanceof

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

3 把参数转换成正确的类型
4 检查每个“关键”域

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

四 例子

public final class PhoneNumber {
    private final short areaCode, prefix, lineNum;

    public PhoneNumber(int areaCode, int prefix, int lineNum) {
        this.areaCode = rangeCheck(areaCode, 999, "area code");
        this.prefix = rangeCheck(prefix, 999, "prefix");
        this.lineNum = rangeCheck(lineNum, 9999, "line num");
    }

    //检查范围
    private static short rangeCheck(int val, int max, String arg) {
        if(val < 0 || val > max) {
            throw new IllegalArgumentException(arg + ":" + val);
        }
        return (short)val;
    }

    @Override
    public boolean equals(Object o) {
        if(o == this) {
            return true;
        }
        if(!(o instanceof PhoneNumber)) {
            return false;
        }
        PhoneNumber pn = (PhoneNumber)o;
        return pn.lineNum == this.lineNum && pn.prefix == this.prefix && pn.areaCode == this.areaCode;
    }
}

编写和测试equals方法都是十分繁琐的,最佳途径是使用 Google 开源的 AutoValue 框架,它会自动生成这些方法,通过类中的单个注解就能触发。

五 总结

不要轻易覆盖 equals 方法,除非迫不得已。
如果覆盖 equals 方法,一定要比较所有关键域,并查看它们是否遵守合约的五个条款。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值