请考虑以下代码段:
import java.util.*;
public class EqualsOverload {
public static void main(String[] args) {
class Thing {
final int x;
Thing(int x) { this.x = x; }
public int hashCode() { return x; }
public boolean equals(Thing other) { return this.x == other.x; }
}
List<Thing> myThings = Arrays.asList(new Thing(42));
System.out.println(myThings.contains(new Thing(42))); // prints "false"
}
}
请注意contains
返回false
!!!我们似乎失去了我们的东西!!
当然,错误是我们意外重载,而不是重写,Object.equals(Object)
。如果我们按照以下方式编写了class Thing
,那么contains
会按预期返回true
。
class Thing {
final int x;
Thing(int x) { this.x = x; }
public int hashCode() { return x; }
@Override public boolean equals(Object o) {
return (o instanceof Thing) && (this.x == ((Thing) o).x);
}
}
Effective Java 2nd Edition,第36项:始终使用覆盖注释,使用基本相同的参数来建议应始终使用@Override
。当然,这个建议很好,因为如果我们试图在第一个片段中声明@Override equals(Thing other)
,我们友好的小编译器会立即指出我们愚蠢的小错误,因为它是一个重载,而不是覆盖。
然而,本书没有具体涵盖的是重载equals
是否是一个好主意。基本上,有3种情况:
第三种情况由以下片段说明:
class Thing {
final int x;
Thing(int x) { this.x = x; }
public int hashCode() { return x; }
public boolean equals(Thing other) { return this.x == other.x; }
@Override public boolean equals(Object o) {
return (o instanceof Thing) && (this.equals((Thing) o));
}
}
在这里,即使我们现在有2个equals
方法,仍然有一个相等逻辑,它位于重载中。 @Override
只是委托过载。
所以问题是:
equals
的理由,或者这几乎肯定是一种不好的做法?答案 0 :(得分:17)
我不会看到重载equals的情况,除了更容易出错且难以维护,尤其是在使用继承时。
在这里,保持反身性,对称性和传递性或检测它们的不一致性是极其困难的,因为你总是必须知道被调用的实际equals方法。想想一个大的继承层次结构,只有一些类型实现自己的重载方法。
所以我说不要这样做。
答案 1 :(得分:8)
如果您的示例中有一个字段,我认为
@Override public boolean equals(Object o) {
return (o instanceof Thing) && (this.x == ((Thing) o).x);
}
是要走的路。任何其他东西都会过于复杂。但是如果你添加一个字段(并且不想通过sun传递80列建议),它看起来就像
@Override public boolean equals(Object o) {
if (!(o instanceof Thing))
return false;
Thing t = (Thing) o;
return this.x == t.x && this.y == t.y;
}
我觉得比
稍微丑陋public boolean equals(Thing o) {
return this.x == o.x && this.y == o.y;
}
@Override public boolean equals(Object o) {
// note that you don't need this.equals().
return (o instanceof Thing) && equals((Thing) o);
}
所以我的经验法则基本上是,如果需要在 override-only 中多次投射,请执行 override- / overload-combo 。
辅助方面是运行时开销。正如Java performance programming, Part 2: The cost of casting所解释的那样:
向下转换操作(在Java语言规范中也称为缩小转换)将祖先类引用转换为子类引用。此转换操作会产生执行开销,因为Java要求在运行时检查转换以确保它是有效的。
通过使用 overload- / override-combo ,在某些情况下(不是全部!)编译器可以在没有向下转换的情况下进行管理。
要评论@Snehal点,暴露两种方法可能会混淆客户端开发人员:另一种选择是让重载的equals是私有的。保留了优雅,方法可以在内部使用,而客户端的接口看起来像预期的那样。
答案 2 :(得分:4)
重载等于的问题:
Java提供的所有集合即; Set,List,Map使用重写方法比较两个对象。因此,即使重载equals方法,也无法解决比较两个对象的问题。此外,如果您只是重载并实现哈希码方法,则会导致错误行为
如果您同时重载和重写equals方法并公开这两种方法,那么您将会混淆客户端开发人员。按照惯例,人们认为你重写了对象类
答案 3 :(得分:2)
书中有许多内容涵盖了这一点。 (它不在我面前,所以我会在记住它们的时候引用物品)
有一个例子正是使用equals(..)
,据说不应该使用重载,如果使用 - 应该小心使用。关于方法设计的项目警告不要使用相同数量的参数重载方法。所以 - 不,不要重载equals(..)
更新:从“有效Java”(第44页)
除了正常方法之外,提供这种“强类型”等于方法是可以接受的,只要这两种方法返回相同的结果,但没有令人信服的理由这样做。
所以,不禁止这样做,但它增加了你的课程的复杂性,同时没有增加收益。
答案 4 :(得分:1)
我在项目中使用这种方法使用覆盖和重载组合,因为代码看起来更清晰。到目前为止,我对这种方法没有任何问题。
答案 5 :(得分:1)
我可以想到一个非常简单的例子,它将无法正常工作以及为什么你永远不应该这样做:
class A {
private int x;
public A(int x) {
this.x = x;
}
public boolean equals(A other) {
return this.x == other.x;
}
@Override
public boolean equals(Object other) {
return (other instanceof A) && equals((A) other);
}
}
class B extends A{
private int y;
public B(int x, int y) {
super(x);
this.y = y;
}
public boolean equals(B other) {
return this.equals((A)other) && this.y == other.y;
}
@Override
public boolean equals(Object other) {
return (other instanceof B) && equals((B) other);
}
}
public class Test {
public static void main(String[] args) {
A a = new B(1,1);
B b1 = new B(1,1);
B b2 = new B(1,2);
// This obviously returns false
System.out.println(b1.equals(b2));
// What should this return? true!
System.out.println(a.equals(b2));
// And this? Also true!
System.out.println(b2.equals(a));
}
}
在此测试中,您可以清楚地看到重载方法在使用继承时弊大于利。在两种错误的情况下,调用更通用的equals(A a)
,因为Java编译器只知道a
类型为A
,并且该对象没有重载的equals(B b)
方法。
事后补充:让重载的equals
私密确实解决了这个问题,但这真的让你感到兴奋吗?它只添加了一个额外的方法,只能通过执行转换来调用。
答案 6 :(得分:1)
让我分享一个重载等于的“ buggy代码”示例:
class A{
private int val;
public A(int i){
this.val = i;
}
public boolean equals(A a){
return a.val == this.val;
}
@Override
public int hashCode() {
return Objects.hashCode(this.val);
}
}
public class TestOverloadEquals {
public static void main(String[] args){
A a1 = new A(1), a2 = new A(2);
List<A> list = new ArrayList<>();
list.add(a1);
list.add(a2);
A a3 = new A(1);
System.out.println(list.contains(a3));
}
}
答案 7 :(得分:0)
由于此问题已有10年历史了,原始作者可能对答案不再感兴趣,因此,我将添加一些信息,这些信息对于如今正在寻找答案的开发人员很有用。
对于Android开发人员,Android Studio包含一个模板,该模板将在尝试重载equal操作符时弹出。它还会生成适当的hashCode方法,并且产生的重写equals
方法将如下所示:
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Thing thing = (Thing) o;
return x.equals(thing.x);
}