Java 中相等性的表现(instanceOf 与 isAssignableFrom)
这个问题具体涉及各种实施方案的性能和某种程度上的简洁性。
我通过这篇关于实现平等权的文章刷新了自己。我的问题特别对应于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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(7)
更新:实际上,您的方法在技术上并不像我最初想象的那样有效,因为它破坏了不覆盖
equals
的子类的equals
对称契约。 code>:原因是
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 overrideequals
:The reason is that
p.getClass().isAssignableFrom(pAnon.getClass())
istrue
while the inverse,pAnon.getClass().isAssignableFrom(p.getClass())
isfalse
.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 oftrue, false, true
like the example in the article.除非您想允许比较不同类型的类,否则最简单、最安全、最简洁且可能最有效的实现是:
unless you want to allow comparing classes of different types, the easiest, safest, most concise and probably most efficient impl is:
到目前为止给出的所有答案都没有回答问题 - 但指出了
equals()
契约。相等必须是等价关系(传递、对称、自反),并且相等的对象必须具有相同的哈希码。这可以完美地扩展到子类 - 前提是子类本身不会覆盖equals()
或hashCode()
。因此,您有两个选择 - 您要么从Point
继承equals()
(因此,如果ColoredPoint
实例具有相同的坐标,则它们是相等的,即使它们具有不同的颜色),或者您重写equals()
(现在必须确保Point
和ColoredPoint
永远不会相等)。如果您需要执行逐点比较,则不要使用
equals()
- 而是编写方法pointwiseEquals()
。无论您选择做什么,您仍然必须在
equals()
中执行类检查。显然是最好的执行者,但如果您希望能够对本身不重写 equals() 的子类进行相等性测试,它确实会破坏(实际上,您可以保证这一点的唯一方法是使类或相等方法成为最终的,并且根本不允许任何子类重写)。如果在
instanceOf
和isAssignableFrom
之间进行选择,则没有实际差异,它们实际上都执行相同的运行时测试(唯一的区别是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 overrideequals()
orhashCode()
. So you have two choices - you either inheritequals()
fromPoint
(soColoredPoint
instances are equal if they have the same coordinates, even if they have a different color), or you overrideequals()
(and now must make sure aPoint
and aColoredPoint
are never equal).If you need to perform a pointwise comparison, then don't use
equals()
- write a methodpointwiseEquals()
instead.Whatever you choose to do, you still have to perform the class check in
equals()
.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 betweeninstanceOf
andisAssignableFrom
, 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 justObject
). 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.请参阅我的回答了解相等和等价之间有什么区别?。
您不能将不同类的两个对象等同,因为它会破坏对称性。
编辑:
这取决于以下内容中的
x
是否与
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: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
.这是我对澄清问题的第二个答案
考虑当我们调用 Point.equals(ColoredPoint cp);
Point.equals() 首先检查
哪些通过。在提供的三个选项中,所有三个选项都会检查另一个对象(在本例中为 ColoredPoint)是否满足更多测试。选项是:
从性能(和设计)的角度来看,检查 Point 的其他实例没有任何价值,因为 OP 想要的实际行为(他无法表达)是针对他的特定用例,这些对象之间的相等意味着它们必须是同一类。
因此,对于性能和设计,只需
按照@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
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:
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
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.
我认为你的解决方案将会失败,因为它不是传递性的 OOPS,对称的。 请参阅《Effective Java》中的章节
我相信(还没有运行您的代码)
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
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.
好的,我们这里有来自《Effective Java》的示例(我有 2008 年第二版)。
该示例位于从第 37 页开始的
第 8 项:在覆盖等于时遵守一般合同
(我写此内容是为了防止您想检查)。class ColoredPoint extends Point{}
并且有 2 个尝试来演示为什么 instanceof 不好。第一次尝试是}
,第二次尝试是
}
首先,永远不会到达第二个 IF。如果“o”不是一个 Point(它是 ColorPoint 的超类),那么非 Point 又怎么会成为 ColorPoint 呢?
所以从一开始的第二次尝试就是错误的!其中真正比较的唯一机会是 super.equals(o) && ((ColorPoint)o).color == color;,这还不够!
这里的解决方案是:
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}
and the second was
}
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:
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.