Java 中相等性的表现(instanceOf 与 isAssignableFrom)

发布于 2024-12-28 01:30:37 字数 2729 浏览 1 评论 0原文

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

我通过这篇关于实现平等权的文章刷新了自己。我的问题特别对应于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
    }
}

this question is specifically about the performance and to some extent brevity of the various implementation alternatives.

I refreshed myself with this article on implementing equality right. My question particularly corresponds to canEqual (to ensure equivalence relation).

instead of overloading canEquals method to use instanceOf in every class in the hierarchy( instance of paramenter is a compile time class ). Why not use isAssignableFrom ( which is resolved dynamically ) in only the top level class. Makes for much concise code and you dont have to overload a third method.

While, this alternative works. Are there any performance considerations that I need to be aware of?

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
    }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

獨角戲 2025-01-04 01:30:37

更新:实际上,您的方法在技术上并不像我最初想象的那样有效,因为它破坏了不覆盖 equals 的子类的 equals 对称契约。 code>:

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 就像文章中的例子一样。

Update: Actually, your method is not technically valid like I first thought, because it breaks the symmetry contract of equals for subclasses that don't override 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

The reason is that p.getClass().isAssignableFrom(pAnon.getClass()) is true while the inverse, pAnon.getClass().isAssignableFrom(p.getClass()) is false.

If you are not convinced by this, try actually running your code and compare it to the version in the article: you will notice that it prints true, false, false, instead of true, false, true like the example in the article.

你列表最软的妹 2025-01-04 01:30:37

除非您想允许比较不同类型的类,否则最简单、最安全、最简洁且可能最有效的实现是:

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

unless you want to allow comparing classes of different types, the easiest, safest, most concise and probably most efficient impl is:

(getClass() == that.getClass())
忆沫 2025-01-04 01:30:37

到目前为止给出的所有答案都没有回答问题 - 但指出了 equals() 契约。相等必须是等价关系(传递、对称、自反),并且相等的对象必须具有相同的哈希码。这可以完美地扩展到子类 - 前提是子类本身不会覆盖 equals()hashCode()。因此,您有两个选择 - 您要么从 Point 继承 equals() (因此,如果 ColoredPoint 实例具有相同的坐标,则它们是相等的,即使它们具有不同的颜色),或者您重写 equals() (现在必须确保 PointColoredPoint 永远不会相等)。

如果您需要执行逐点比较,则不要使用 equals() - 而是编写方法 pointwiseEquals()

无论您选择做什么,您仍然必须在 equals() 中执行类检查。

getClass() == that.getClass()

显然是最好的执行者,但如果您希望能够对本身不重写 equals() 的子类进行相等性测试,它确实会破坏(实际上,您可以保证这一点的唯一方法是使类或相等方法成为最终的,并且根本不允许任何子类重写)。如果在 instanceOfisAssignableFrom 之间进行选择,则没有实际差异,它们实际上都执行相同的运行时测试(唯一的区别是 instanceOf< /code> 可以执行编译时健全性检查,但在这种情况下,当输入只是Object时,它无法知道任何内容。在这两种情况下,运行时检查是相同的 - 检查对象列出的接口中的目标类(这不适用于此处,因为我们不检查接口),或者沿着类层次结构向上查找,直到找到列出类或追根溯源。

All the answers given so far don't answer the question - but point out the equals() contract. Equality must be an equivalence relation (transitive, symmetric, reflexive) and equal objects must have the same hash code. That extends perfectly fine to subclasses - provided subclasses don't themselves override equals() or hashCode(). So you have two choices - you either inherit equals() from Point (so ColoredPoint instances are equal if they have the same coordinates, even if they have a different color), or you override equals() (and now must make sure a Point and a ColoredPoint are never equal).

If you need to perform a pointwise comparison, then don't use equals() - write a method pointwiseEquals() instead.

Whatever you choose to do, you still have to perform the class check in equals().

getClass() == that.getClass()

is clearly the best performer, but it does break if you expect to be able to equality test subclasses that don't themselves override equals() (and in practice, the only way you can guarantee that is to make the class or the equality methods final and not allow any subclasses to override at all). If it's a choice between instanceOf and isAssignableFrom, there's no practical difference, they both in fact perform the same run-time test (the only difference is, instanceOf can perform a compile-time sanity check, but in this case, it can't know anything when the input is just Object). In both cases, the runtime check is identical - check for the target class in the object's listed interfaces (which doesn't apply here, since we're not checking for an interface), or walk up the class hierarchy until we either find the listed class or get to the root.

心在旅行 2025-01-04 01:30:37

请参阅我的回答了解相等和等价之间有什么区别?

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

编辑

这取决于以下内容中的x是否

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

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

getClass() == that.getClass()具有相同的功能。
根据 @waxwing 的回答没有。

即使它是正确的,我也没有看到调用 that.getClass().isAssignableFrom 有任何性能优势。

See my answer for What is the difference between equality and equivalence?.

You can't equate two objects from different classes because it breaks symmetry.

Edit:

It comes down to whether x in the following:

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

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

has the same power as getClass() == that.getClass().
According to @waxwing's answer it doesn't.

Even if it were correct, I don't see any performance benefit here by calling that.getClass().isAssignableFrom.

末骤雨初歇 2025-01-04 01:30:37

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

考虑当我们调用 Point.equals(ColoredPoint cp);

Point.equals() 首先检查

if (other instanceof Point)...

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

  1. 仅当 Point 是 ColoredPoint 的实例时才为 true,这永远不会为
  2. true 仅当 ColoredPoint 可从 Point 分配时才会为 true,这永远
  3. 不会为 true。

从性能(和设计)的角度来看,检查 Point 的其他实例没有任何价值,因为 OP 想要的实际行为(他无法表达)是针对他的特定用例,这些对象之间的相等意味着它们必须是同一类。

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

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

按照@jthalborn的建议

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

Here's my Second Answer to the clarified question

Consider when we call Point.equals(ColoredPoint cp);

Point.equals() first checks for

if (other instanceof Point)...

Which passes. Out of the three options presented, all three of them check that the other object, in this case a ColoredPoint, satisfies some more test. The options are:

  1. will only be true if Point is an instanceof ColoredPoint, which is never
  2. will only be true if ColoredPoint is assignable from Point, which is never
  3. will never be true.

From a performance (and design) perspective, there was no value in checking for other instanceof Point, because the actual behavior OP wants (which he has been unable to express) is that for his particular use case, equality between these Objects means they must be the same class.

Therefore, for both performance and design, just use

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

as was suggested by @jthalborn

When a later coder sees instanceof or isAssignableFrom in your code, he will think that subclasses are allowed to equal the base class, which is completely misleading.

眉黛浅 2025-01-04 01:30:37

我认为你的解决方案将会失败,因为它不是传递性的 OOPS,对称的。 请参阅《Effective Java》中的章节

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

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

p.equals(cp) 是 true

cp.equals(p) 是 false

虽然我不完全理解你的代码 - 它指的是被注释掉的 canEquals() 。简而言之,您要么必须忽略颜色以实现平等,要么必须按照 @jthalborn 的建议进行操作。

I think you solution will fail because it isn't transitive OOPS, symmetric. See The chapter from Effective Java

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

I believe (haven't run your code) that

p.equals(cp) is true

but

cp.equals(p) is false

Though I don't fully understand your code - it refers to canEquals() which was commented out. The short answer is that you either have to ignore color for equality, or you have to do what @jthalborn suggested.

不如归去 2025-01-04 01:30:37

好的,我们这里有来自《Effective Java》的示例(我有 2008 年第二版)。
该示例位于从第 37 页开始的第 8 项:在覆盖等于时遵守一般合同(我写此内容是为了防止您想检查)。

class ColoredPoint extends Point{} 并且有 2 个尝试来演示为什么 instanceof 不好。第一次尝试是

// 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”不是一个 Point(它是 ColorPoint 的超类),那么非 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(),但您的实现取决于您的范围。如何定义两个对象相等或不相等?实施它,它将相应地发挥作用。

OK we here we have the example from Effective Java (i have the 2nd Edition 2008).
The example is in ITEM 8: OBEY THE GENERAL CONTRACT WHEN OVERRIDING EQUALS starting from page 37 (I write this in case you want to check).

class ColoredPoint extends Point{} and there are 2 attepts in demostrating why instanceof is BAD. The first attempt was

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

}

and the second was

 // 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;

}

First of all the second IF will never be reached. If 'o' is not a Point which is a superclass to ColorPoint how might it happen a non-Point to be a ColorPoint ??????

So the second attempt from the beginning is wrong ! Where the only chance for a TRUE comparison is super.equals(o) && ((ColorPoint)o).color == color;, which is not enough !!
a solution here would be:

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() is used for very specific equals(), But your implementations depends of your scope. How do you define that two objects are equal or not? Implement it and it will work accordingly.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文