| 订阅 | 在线投稿
分享
 
 
 

深入equals方法

来源:互联网  宽屏版  评论
2007-02-02 20:08:57

深入equals方法

equals方法的重要性毋须多言,只要你想比较的两个对象不愿是同一对象,你就应该实现

equals方法,让对象用你认为相等的条件来进行比较.

下面的内容只是API的规范,没有什么太高深的意义,但我之所以最先把它列在这儿,是因为

这些规范在事实中并不是真正能保证得到实现.

1.对于任何引用类型, o.equals(o) == true成立.

2.如果 o.equals(o1) == true 成立,那么o1.equals(o)==true也一定要成立.

3.如果 o.equals(o1) == true 成立且 o.equals(o2) == true 成立,那么

o1.equals(o2) == true 也成立.

4.如果第一次调用o.equals(o1) == true成立再o和o1没有改变的情况下以后的任何次调用

都成立.

5.o.equals(null) == true 任何时间都不成立.

以上几条规则并不是最完整的表述,详细的请参见API文档.

对于Object类,它提供了一个最最严密的实现,那就是只有是同一对象是,equals方法才返回

true,也就是人们常说的引用比较而不是值比较.这个实现严密得已经没有什么实际的意义,

所以在具体子类(相对于Object来说)中,如果我们要进行对象的值比较,就必须实现自己的

equals方法.

先来看一下以下这段程序:

public boolean equals(Object obj)

{

if (obj == null) return false;

if (!(obj instanceof FieldPosition))

return false;

FieldPosition other = (FieldPosition) obj;

if (attribute == null) {

if (other.attribute != null) {

return false;

}

}

else if (!attribute.equals(other.attribute)) {

return false;

}

return (beginIndex == other.beginIndex

&& endIndex == other.endIndex

&& field == other.field);

}

这是JDK中java.text.FieldPosition的标准实现,似乎没有什么可说的.

我信相大多数或绝大多数程序员认为,这是正确的合法的equals实现.毕竟它是JDK的API实现啊.

还是让我们以事实来说话吧:

package debug;

import java.text.*;

public class Test {

public static void main(String[] args) {

FieldPosition fp = new FieldPosition(10);

FieldPosition fp1 = new MyTest(10);

System.out.println(fp.equals(fp1));

System.out.println(fp1.equals(fp));

}

}

class MyTest extends FieldPosition{

int x = 10;

public MyTest(int x){

super(x);

this.x = x;

}

public boolean equals(Object o){

if(o==null) return false;

if(!(o instanceof MyTest )) return false;

return ((MyTest)o).x == this.x;

}

}

运行一下看看会打印出什么:

System.out.println(fp.equals(fp1));打印true

System.out.println(fp1.equals(fp));打印flase

两个对象,出现了不对称的equals算法.问题出在哪里(脑筋急转弯:当然出在JDK实现的BUG)?

我相信有太多的程序员(除了那些根本不知道实现equals方法的程序员外)在实现equals方法

时都用过instanceof运行符来进行短路优化的,实事求是地说很长一段时间我也这么用过。

太多的教程,文档都给了我们这样的误导。而有些稍有了解的程序员可能知道这样的优化可能

有些不对但找不出问题的关键。另外一种极端是知道这个技术缺陷的骨灰级专家就提议不要这

样应用。

我们知道,"通常"要对两个对象进行比较,那么它们"应该"是同一类型。所以首先利用instanceof

运行符进行短路优化,如果被比较的对象不和当前对象是同一类型则不用比较返回false,但事实

上,"子类是父类的一个实例",所以如果 子类 o instanceof 父类,始终返回true,这时肯定

不会发生短路优化,下面的比较有可能出现多种情况,一种是不能造型成子类而抛出异常,另一种

是父类的private 成员没有被子类继承而不能进行比较,还有就是形成上面这种不对称比较。可能

会出现太多的情况。

那么,是不是就不能用 instanceof运行符来进行优化?答案是否定的,JDK中仍然有很多实现是正

确的,如果一个class是final的,明知它不可能有子类,为什么不用 instanceof来优化呢?

为了维护SUN的开发小组的声誉,我不说明哪个类中,但有一个小组成员在用这个方法优化时在后加

加上了加上了这样的注释:

if (this == obj) // quick check

return true;

if (!(obj instanceof XXXXClass)) // (1) same object?

return false;

可能是有些疑问,但不知道如何做(不知道为什么没有打电话给我......)

那么对于非final类,如何进行类型的quick check呢?

if(obj.getClass() != XXXClass.class) return false;

用被比较对象的class对象和当前对象的class比较,看起来是没有问题,但是,如果这个类的子类

没有重新实现equals方法,那么子类在比较的时候,obj.getClass() 肯定不等于XXXCalss.class,

也就是子类的equals将无效,所以if(obj.getClass() != this.getClass()) return false;才是正

确的比较。

另外一个quick check是if(this==obj) return true;

是否equals方法一定比较的两个对象就一定是要同一类型?上面我用了"通常",这也是绝大多数程序

员的愿望,但是有些特殊的情况,我们可以进行不同类型的比较,这并不违反规范。但这种特殊情况

是非常罕见的,一个不恰当的例子是,Integer类的equals可以和Sort做比较,比较它们的value是不

是同一数学值。(事实上JDK的API中并没有这样做,所以我才说是不恰当的例子)

在完成quick check以后,我们就要真正实现你认为的“相等”。对于如果实现对象相等,没有太高

的要求,比如你自己实现的“人”类,你可以认为只要name相同即认为它们是相等的,其它的sex,

ago都可以不考虑。这是不完全实现,但是如果是完全实现,即要求所有的属性都是相同的,那么如

何实现equals方法?

class Human{

private String name;

private int ago;

private String sex;

....................

public boolean equals(Object obj){

quick check.......

Human other = (Human)ojb;

return this.name.equals(other.name)

&& this.ago == ohter.ago

&& this.sex.equals(other.sex);

}

}

这是一个完全实现,但是,有时equals实现是在父类中实现,而要求被子类继承后equals能正确的工

作,这时你并不事实知道子类到底扩展了哪些属性,所以用上面的方法无法使equals得到完全实现。

一个好的方法是利用反射来对equals进行完全实现:

public boolean equals(Object obj){

quick check.......

Class c = this.getClass();

Filed[] fds = c.getDeclaredFields();

for(Filed f:fds){

if(!f.get(this).equals(f.get(obj)))

return false;

}

return true;

}

为了说明的方便,上明的实现省略了异常,这样的实现放在父类中,可以保证你的子类的equals可以按

你的愿望正确地工作。

关于equals方法的最后一点是:如果你要是自己重写(正确说应该是履盖)了equals方法,那同时就一

定要重写hashCode().为是规范,否则.............

我们还是看一下这个例子:

public final class PhoneNumber {

private final int areaCode;

private final int exchange;

private final int extension;

public PhoneNumber(int areaCode, int exchange, int extension) {

rangeCheck(areaCode, 999, "area code");

rangeCheck(exchange, 99999999, "exchange");

rangeCheck(extension, 9999, "extension");

this.areaCode = areaCode;

this.exchange = exchange;

this.extension = extension;

}

private static void rangeCheck(int arg, int max, String name) {

if(arg < 0 || arg > max)

throw new IllegalArgumentException(name + ": " + arg);

}

public boolean equals(Object o) {

if(o == this)

return true;

if(!(o instanceof PhoneNumber))

return false;

PhoneNumber pn = (PhoneNumber)o;

return pn.extension == extension && pn.exchange == exchange && pn.areaCode == areaCode;

}

}

注意这个类是final的,所以这个equals实现没有什么问题。

我们来测试一下:

public static void main(String[] args) {

Map hm = new HashMap();

PhoneNumber pn = new PhoneNumber(123, 38942, 230);

hm.put(pn, "I love you");

PhoneNumber pn1 = new PhoneNumber(123, 38942, 230);

System.out.println(pn);

System.out.println("pn.equals(pn1) is " + pn.equals(pn1));

System.out.println(hm.get(pn1));

System.out.println(hm.get(pn));

}

既然pn.equals(pn1),那么我put(pn,"I love you");后,get(pn1)这什么是null呢?

答案是因为它们的hashCode不一样,而hashMap就是以hashCode为主键的。

所以规范要求,如果两个对象进行equals比较时如果返回true,那么它们的hashcode要求返回相等的值。

好了,休息,休息一下。。。。。。。。。。。。。。。。

深入equals方法 equals方法的重要性毋须多言,只要你想比较的两个对象不愿是同一对象,你就应该实现 equals方法,让对象用你认为相等的条件来进行比较. 下面的内容只是API的规范,没有什么太高深的意义,但我之所以最先把它列在这儿,是因为 这些规范在事实中并不是真正能保证得到实现. 1.对于任何引用类型, o.equals(o) == true成立. 2.如果 o.equals(o1) == true 成立,那么o1.equals(o)==true也一定要成立. 3.如果 o.equals(o1) == true 成立且 o.equals(o2) == true 成立,那么 o1.equals(o2) == true 也成立. 4.如果第一次调用o.equals(o1) == true成立再o和o1没有改变的情况下以后的任何次调用 都成立. 5.o.equals(null) == true 任何时间都不成立. 以上几条规则并不是最完整的表述,详细的请参见API文档. 对于Object类,它提供了一个最最严密的实现,那就是只有是同一对象是,equals方法才返回 true,也就是人们常说的引用比较而不是值比较.这个实现严密得已经没有什么实际的意义, 所以在具体子类(相对于Object来说)中,如果我们要进行对象的值比较,就必须实现自己的 equals方法. 先来看一下以下这段程序: public boolean equals(Object obj) { if (obj == null) return false; if (!(obj instanceof FieldPosition)) return false; FieldPosition other = (FieldPosition) obj; if (attribute == null) { if (other.attribute != null) { return false; } } else if (!attribute.equals(other.attribute)) { return false; } return (beginIndex == other.beginIndex && endIndex == other.endIndex && field == other.field); } 这是JDK中java.text.FieldPosition的标准实现,似乎没有什么可说的. 我信相大多数或绝大多数程序员认为,这是正确的合法的equals实现.毕竟它是JDK的API实现啊. 还是让我们以事实来说话吧: package debug; import java.text.*; public class Test { public static void main(String[] args) { FieldPosition fp = new FieldPosition(10); FieldPosition fp1 = new MyTest(10); System.out.println(fp.equals(fp1)); System.out.println(fp1.equals(fp)); } } class MyTest extends FieldPosition{ int x = 10; public MyTest(int x){ super(x); this.x = x; } public boolean equals(Object o){ if(o==null) return false; if(!(o instanceof MyTest )) return false; return ((MyTest)o).x == this.x; } } 运行一下看看会打印出什么: System.out.println(fp.equals(fp1));打印true System.out.println(fp1.equals(fp));打印flase 两个对象,出现了不对称的equals算法.问题出在哪里(脑筋急转弯:当然出在JDK实现的BUG)? 我相信有太多的程序员(除了那些根本不知道实现equals方法的程序员外)在实现equals方法 时都用过instanceof运行符来进行短路优化的,实事求是地说很长一段时间我也这么用过。 太多的教程,文档都给了我们这样的误导。而有些稍有了解的程序员可能知道这样的优化可能 有些不对但找不出问题的关键。另外一种极端是知道这个技术缺陷的骨灰级专家就提议不要这 样应用。 我们知道,"通常"要对两个对象进行比较,那么它们"应该"是同一类型。所以首先利用instanceof 运行符进行短路优化,如果被比较的对象不和当前对象是同一类型则不用比较返回false,但事实 上,"子类是父类的一个实例",所以如果 子类 o instanceof 父类,始终返回true,这时肯定 不会发生短路优化,下面的比较有可能出现多种情况,一种是不能造型成子类而抛出异常,另一种 是父类的private 成员没有被子类继承而不能进行比较,还有就是形成上面这种不对称比较。可能 会出现太多的情况。 那么,是不是就不能用 instanceof运行符来进行优化?答案是否定的,JDK中仍然有很多实现是正 确的,如果一个class是final的,明知它不可能有子类,为什么不用 instanceof来优化呢? 为了维护SUN的开发小组的声誉,我不说明哪个类中,但有一个小组成员在用这个方法优化时在后加 加上了加上了这样的注释: if (this == obj) // quick check return true; if (!(obj instanceof XXXXClass)) // (1) same object? return false; 可能是有些疑问,但不知道如何做(不知道为什么没有打电话给我......) 那么对于非final类,如何进行类型的quick check呢? if(obj.getClass() != XXXClass.class) return false; 用被比较对象的class对象和当前对象的class比较,看起来是没有问题,但是,如果这个类的子类 没有重新实现equals方法,那么子类在比较的时候,obj.getClass() 肯定不等于XXXCalss.class, 也就是子类的equals将无效,所以if(obj.getClass() != this.getClass()) return false;才是正 确的比较。 另外一个quick check是if(this==obj) return true; 是否equals方法一定比较的两个对象就一定是要同一类型?上面我用了"通常",这也是绝大多数程序 员的愿望,但是有些特殊的情况,我们可以进行不同类型的比较,这并不违反规范。但这种特殊情况 是非常罕见的,一个不恰当的例子是,Integer类的equals可以和Sort做比较,比较它们的value是不 是同一数学值。(事实上JDK的API中并没有这样做,所以我才说是不恰当的例子) 在完成quick check以后,我们就要真正实现你认为的“相等”。对于如果实现对象相等,没有太高 的要求,比如你自己实现的“人”类,你可以认为只要name相同即认为它们是相等的,其它的sex, ago都可以不考虑。这是不完全实现,但是如果是完全实现,即要求所有的属性都是相同的,那么如 何实现equals方法? class Human{ private String name; private int ago; private String sex; .................... public boolean equals(Object obj){ quick check....... Human other = (Human)ojb; return this.name.equals(other.name) && this.ago == ohter.ago && this.sex.equals(other.sex); } } 这是一个完全实现,但是,有时equals实现是在父类中实现,而要求被子类继承后equals能正确的工 作,这时你并不事实知道子类到底扩展了哪些属性,所以用上面的方法无法使equals得到完全实现。 一个好的方法是利用反射来对equals进行完全实现: public boolean equals(Object obj){ quick check....... Class c = this.getClass(); Filed[] fds = c.getDeclaredFields(); for(Filed f:fds){ if(!f.get(this).equals(f.get(obj))) return false; } return true; } 为了说明的方便,上明的实现省略了异常,这样的实现放在父类中,可以保证你的子类的equals可以按 你的愿望正确地工作。 关于equals方法的最后一点是:如果你要是自己重写(正确说应该是履盖)了equals方法,那同时就一 定要重写hashCode().为是规范,否则............. 我们还是看一下这个例子: public final class PhoneNumber { private final int areaCode; private final int exchange; private final int extension; public PhoneNumber(int areaCode, int exchange, int extension) { rangeCheck(areaCode, 999, "area code"); rangeCheck(exchange, 99999999, "exchange"); rangeCheck(extension, 9999, "extension"); this.areaCode = areaCode; this.exchange = exchange; this.extension = extension; } private static void rangeCheck(int arg, int max, String name) { if(arg < 0 || arg > max) throw new IllegalArgumentException(name + ": " + arg); } public boolean equals(Object o) { if(o == this) return true; if(!(o instanceof PhoneNumber)) return false; PhoneNumber pn = (PhoneNumber)o; return pn.extension == extension && pn.exchange == exchange && pn.areaCode == areaCode; } } 注意这个类是final的,所以这个equals实现没有什么问题。 我们来测试一下: public static void main(String[] args) { Map hm = new HashMap(); PhoneNumber pn = new PhoneNumber(123, 38942, 230); hm.put(pn, "I love you"); PhoneNumber pn1 = new PhoneNumber(123, 38942, 230); System.out.println(pn); System.out.println("pn.equals(pn1) is " + pn.equals(pn1)); System.out.println(hm.get(pn1)); System.out.println(hm.get(pn)); } 既然pn.equals(pn1),那么我put(pn,"I love you");后,get(pn1)这什么是null呢? 答案是因为它们的hashCode不一样,而hashMap就是以hashCode为主键的。 所以规范要求,如果两个对象进行equals比较时如果返回true,那么它们的hashcode要求返回相等的值。 好了,休息,休息一下。。。。。。。。。。。。。。。。
󰈣󰈤
 
 
 
>>返回首页<<
 
 热帖排行
 
 
王朝网络微信公众号
微信扫码关注本站公众号wangchaonetcn
 
 
静静地坐在废墟上,四周的荒凉一望无际,忽然觉得,凄凉也很美
©2005- 王朝网络 版权所有