1. 重写equals方法的要求
public boolean equals(Object obj)
指明与参数obj指定的对象是否相等,相等返回true,否则返回false.
关于equals方法的实现,java API文档中标明了以下几点要求。
·自反性。 对于任何非null的引用值 x,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。
·一致性。 对于任何非空引用值x与y,假设对象上equals比较的信息没有被修改,则多次调用x.equals(y)始终返回true或始终返回false。
·对于任何非空引用值 x,x.equals(null)应返回false.
了解equals的规则后,现在思考这样一个问题:equals方法比较的是什么?
有些初学者受String类的影响,认为equals方法比较的是对象的内容,而“==”比较的是对象的地址,即两个引用是否指向同一个对象所在的内存地址,这是不对的。
之所以String类的equals方法比较的是对象的内容(即字符串的内容)而非地址,是因为String类重写了equals方法,而如果默认的equals方法来讲(也就是从Object类继承的equals方法),
其比较的也是对象的地址,即默认的equals方法与“==”的比较方式是一样的。
请看Object类中equals的方法的实现:
实现很简单,实际上就是使用“==”来判断obj与this是否指向相同的对象。
下例中说明了equals方法的比较
程序运行结果如下:
String 类因为重写了equals方法,所以其比较的是对象的内容,而不是对象的地址,而str1与str2指向的字符串是相等的,因此,equals比较的结果为true.
尽管两个Box的引用box1和box2指向的对象中对应的成员变量都是相等的,但是Box类并没有重写equals方法,而使用的是从Object类中继承的equals方法(比较的是对象的地址),因此返回false。
当然,如果执行box2=box1,则box1和box2两个引用同时指向了以前box1指向的对象,自然再次调用equals时返回true.
通常,我们应该重写equals方法,就像String类那样,使得equals方法比较的是对象的内容而不是对象的地址。
可以这样重写Box类的equals方法:
对于非Box类的对象,应该认为是不相等的,这里使用instanceof来判断引用obj所指向对象的类型。
然后比较两个对象的成员变量,如果相等则返回true,否则返回false。
equals方法中并没有对obj为null的情况做判断,因为该判读已经隐藏在instanceof中了,如果:
则:
恒为false。
2.equals方法分析
如果只用在上面的程序中,equals方法可以运行正常,不过,如果期望子类与父类的对象之间可以进行混合比较时,在equals方法中仅仅使用instanceof运算符来判断还不够,下例中介绍这种情况。
程序中定义了Line(线)、Curve(曲线),并且在各自的类中重写了equals方法,程序期望实现以下要求。
(1)当相同类型的对象比较时;
·如果两条线(Line)的长度相等,则两条线相等,否则不相等;
·如果两条曲线(Curve)的长度相等,则两条曲线相等,否则不相等。
(2)当父类与子类(这里指线与曲线)进行混合比较时;
·如果线与曲线的长度相等,则相等,否则不相等。
(3)对于其他类型的比较,认为不相等。
程序实现:
程序运行结果:
在equals中使用instanceof运算符时,出现了一点小问题,例如在Line类中:
如果该表达式值为true,那么只能说明可以将obj转换成Line类型,却不能说obj引用一定指向Line类的对象,
当obj指向Curve类的对象时( Line的子类),该表达式值同样为true,
例如:
两次输出true。因此,使用instanceof运算符并不能判断出某引用所指向对象的准确类型,而只能得出对象是instanceof运算符右侧操作数的类型或右侧操作数的子类型。
因此一旦进行父类与子类的比较时,就会违背equals方法对称性原则。
所以,在equals方法中,当使用instanceof对参数类型做判断时,如果参数类型并非当前类的类型,不能直接返回 false,而是应该递归地调用父类的equals方法继续做相等的判断,例如Curve类的equals方法,应该这样修改:
于是上面例子的修正:
运行结果:
这样修改后,equals方法也就符合对称性的规则了,并且当子类对象与父类对象混合比较时,也遵守传递性。但是,这种方式依然不是完美的,这种传递性只是限定在子类没有新增成员变量或者子类中新增的成员变量没有作用在equals方法的比较中。一旦子类的equals方法使用了新增的成员变量作为比较条件,则当进行父类与子类对象的混合比较时,equals方法就会变得很脆弱了。
例:
运行结果:
从而得知,在equals方法中使用了子类新增的成员作为比较,当父类与子类对象进行混合比较时,就会与传递性相矛盾,而这种情况并不能通过修改equals方法解决,如果可能,当子类中新增成员,而该成员又作为equals方法的比较条件时,最好避免这样的混合比较。