服务热线:13616026886

技术文档 欢迎使用技术文档,我们为你提供从新手到专业开发者的所有资源,你也可以通过它日益精进

位置:首页 > 技术文档 > JAVA > 新手入门 > 基础入门 > 查看文档

java进阶:深入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;才是正确的比较。

扫描关注微信公众号