Java中的平等性(instanceOf vs isAssignableFrom)

时间:2012-01-20 20:36:37

标签: java oop scala jvm equality

这个问题具体涉及各种实施方案的性能和某种程度的简洁性。

我用this article对实现平等权利进行了更新。我的问题特别对应canEqual(以确保等价关系)。

而不是重载canEquals方法以在层次结构中的每个类中使用instanceOf(paramenter的实例是编译时类)。为什么不在顶级类中使用isAssignableFrom(动态解析)。使得更简洁的代码,你不必重载第三种方法。

虽然这个替代方案有效。我需要注意哪些性能方面的考虑因素?

enum Color {
    RED, ORANGE, YELLOW, GREEN, BLUE, INDIGO, VIOLET;
}
class Point {

    int x;
    int y;

    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }


    @Override public boolean equals(Object other) {
        boolean result = false;
        if (other instanceof Point) {
            Point that = (Point) other;
            //Option 1
            //result = (that.canEqual(this) && this.getX() == that.getX() && this.getY() == that.getY());
            //Option 2
            //result = (that.getClass().isAssignableFrom(this.getClass()) && this.getX() == that.getX() && this.getY() == that.getY());
            //Option 3
            //result = (getClass() == that.getClass() && this.getX() == that.getX() && this.getY() == that.getY());
        }
        return result;
    }

    @Override public int hashCode() {
        return (41 * (41 + x) + y);
    }

    public boolean canEqual(Object other) {  return (other instanceof Point);   }
}

public class ColoredPoint extends Point{
      Color color;

        public ColoredPoint(int x, int y, Color color) {
            super(x, y);
            this.color = color;
        }

        @Override public boolean equals(Object other) {
            boolean result = false;
            if (other instanceof ColoredPoint) {
                ColoredPoint that = (ColoredPoint) other;
                result = (this.color.equals(that.color) && super.equals(that));
            }
            return result;
        }

        @Override public int hashCode() {
            return (41 * super.hashCode() + color.hashCode());
        }

        @Override public boolean canEqual(Object other) {    return (other instanceof ColoredPoint);   }

    public static void main(String[] args) {
        Object p = new Point(1, 2);
        Object cp = new ColoredPoint(1, 2, Color.INDIGO);

        Point pAnon = new Point(1, 1) {
            @Override public int getY() {
                return 2;
            }
        };

        Set<Point> coll = new java.util.HashSet<Point>();
        coll.add((Point)p);

        System.out.println(coll.contains(p)); // prints true
        System.out.println(coll.contains(cp)); // prints false
        System.out.println(coll.contains(pAnon)); // prints true
    }
}

7 个答案:

答案 0 :(得分:4)

更新:实际上,您的方法在技术上并不像我首先想到的那样有效,因为它违反了equals对于不覆盖equals的子类的对称性约定:

Point p = new Point(1, 2);
Point pAnon = new Point(1, 1) {
    @Override public int getY() {
        return 2;
    }
};

System.out.println(p.equals(pAnon)); // prints false
System.out.println(pAnon.equals(p)); // prints true

原因是p.getClass().isAssignableFrom(pAnon.getClass())true,反之,pAnon.getClass().isAssignableFrom(p.getClass())false

如果您不相信这一点,请尝试实际运行您的代码并将其与文章中的版本进行比较:您会注意到它打印true, false, false,而不是true, false, true,如同制品

答案 1 :(得分:3)

除非你想允许比较不同类型的类,否则最简单,最安全,最简洁,最有效的方法是:

(getClass() == that.getClass())

答案 2 :(得分:1)

my answerWhat is the difference between equality and equivalence?

您不能将来自不同类的两个对象等同起来,因为它会破坏对称性。

修改

归结为以下x

if (other instanceof Point) {
   Point that = (Point) other;

   boolean x = that.getClass().isAssignableFrom(this.getClass());
}

getClass() == that.getClass()具有相同的权力。 根据{{​​3}}的答案,它没有。

即使它是正确的,我也不会通过调用that.getClass().isAssignableFrom来看到任何性能优势。

答案 3 :(得分:1)

到目前为止给出的所有答案都没有回答这个问题 - 但指出了equals()合同。等式必须是等价关系(传递,对称,自反),并且相等的对象必须具有相同的哈希码。这完全适用于子类 - 提供的子类本身不会覆盖equals()hashCode()。所以你有两个选择 - 要么从equals()继承Point(所以如果ColoredPoint实例具有相同的坐标,即使它们具有不同的颜色,它们是相等的),或者你覆盖{ {1}}(现在必须确保equals()Point永远不相等。

如果您需要执行逐点比较,请不要使用ColoredPoint - 请改为编写方法equals()

无论您选择做什么,您仍然需要在pointwiseEquals()中执行课程检查。

equals()

显然是表现最好的,但是如果你希望能够通过不会覆盖getClass() == that.getClass() 的相等测试子类,它确实会破坏(实际上,你可以保证唯一的方法就是class或者等式final,并且根本不允许任何子类重写。如果它是equals()instanceOf之间的选择,则没有实际差异,它们实际上都执行相同的运行时测试(唯一的区别是,isAssignableFrom 可以执行编译时健全性检查,但在这种情况下,当输入只是instanceOf时,它无法知道任何事情。在这两种情况下,运行时检查都是相同的 - 检查对象列出的接口中的目标类(这里不适用,因为我们没有检查接口),或者在类层次结构中向上走,直到找到列出的课程或到达根目录。

答案 4 :(得分:1)

这是我对澄清问题的第二个答案

考虑我们何时致电Point.equals(ColoredPoint cp);

Point.equals()首先检查

if (other instanceof Point)...

哪个过去了。在提出的三个选项中,所有三个选项都检查另一个对象(在本例中为ColoredPoint)是否满足更多测试。选项包括:

  1. 只有在Point是ColoredPoint的一个实例时才会成立,它永远不会是
  2. 如果ColoredPoint可以从Point分配,那么
  3. 只会是真的,这绝不是
  4. 永远不会成真。
  5. 从性能(和设计)的角度来看,检查other instanceof Point没有价值,因为OP想要的实际行为(他一直无法表达)是针对他的特定用例,这些对象意味着它们必须是同一个类。

    因此,对于性能和设计,只需使用

     this.getClass() == that.getClass()
    

    正如@jthalborn所建议的

    当后来的编码人员在你的代码中看到instanceof或isAssignableFrom时,他会认为允许子类等于基类,这完全是误导。

答案 5 :(得分:0)

我认为你的解决方案会失败,因为它不是传递OOPS,是对称的。 See The chapter from Effective Java

Point p = new Point(2,3);
ColoredPoint cp = new ColoredPoint(2,3, Color.WHITE);

我相信(没有运行你的代码)

p.equals(cp)为真

cp.equals(p)为假

虽然我不完全理解你的代码 - 它指的是被注释掉的canEquals()。简短的回答是,你要么必须忽视颜色的平等,要么你必须做@jthalborn所建议的。

答案 6 :(得分:0)

好的,我们这里有Effective Java的例子(我有2008年第2版)。 该示例位于ITEM 8: OBEY THE GENERAL CONTRACT WHEN OVERRIDING EQUALS从第37页开始(我写这个以防您想要检查)。

class ColoredPoint extends Point{}并且有2个参与者在演示为什么instanceof是BAD。第一次尝试是

// Broken - violates symmetry!
@Override public boolean equals(Object o) {
          if (!(o instanceof ColorPoint))
          return false;
          return super.equals(o) && ((ColorPoint) o).color == color;

}

,第二个是

 // Broken - violates transitivity!
@Override public boolean equals(Object o) {
if (!(o instanceof Point))
    return false;
// If o is a normal Point, do a color-blind comparison
if (!(o instanceof ColorPoint))
    return o.equals(this);
// o is a ColorPoint; do a full comparison
return super.equals(o) && ((ColorPoint)o).color == color;

}

首先,永远不会达到第二个IF。如果'o'不是作为ColorPoint超类的Point,那么如何将非Point作为ColorPoint ??????

所以从一开始的第二次尝试是错误的!真正比较的唯一机会是super.equals(o) && ((ColorPoint)o).color == color;,这还不够! 这里的解决方案是:

if (super.equals(o)) return true;

  if (!(o instanceof ColorPoint))  
      if ((o instanceof Point)) return this.equals(o); 
      else return false;

  return (color ==((ColorPoint)o).color && this.equals(o));

obj.getClass()用于非常特定的equals(),但您的实现取决于您的范围。你如何定义两个对象是否相等?实施它,它将相应地工作。