关于平等的最佳做法:过载还是不过载?

请考虑以下代码段:

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

有效的Java第2版,第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的理由,或者这几乎肯定是一种不好的做法?

我没有看到重载equals的情况,除了更容易出错并且更难维护,尤其是在使用inheritance时。

在这里,保持反身性,对称性和传递性或检测它们的不一致性是极其困难的,因为你总是必须知道被调用的实际equals方法。 想想一个大的inheritance层次结构,只有一些类型实现自己的重载方法。

所以我会说不要这样做。

如果您的示例中有一个单独的字段,我想

 @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 == tx && this.y == ty; } 

我认为这比稍微有些丑陋

 public boolean equals(Thing o) { return this.x == ox && this.y == oy; } @Override public boolean equals(Object o) { // note that you don't need this.equals(). return (o instanceof Thing) && equals((Thing) o); } 

所以我的经验法则基本上是,如果需要在覆盖中多次强制转换 ,请执行override- / overload-combo


第二个方面是运行时开销。 作为Java性能编程,第2部分:构建成本解释:

向下转换操作 (在Java语言规范中也称为缩小转换)将祖先类引用转换为子类引用。 此转换操作会产生执行开销,因为Java要求在运行时检查转换以确保它是有效的。

通过使用overload- / override-combo ,编译器在某些情况下(并非全部!)可以在没有downcast的情况下进行管理。


要评论@Snehal点,暴露两种方法可能会混淆客户端开发人员:另一种选择是让重载的equals是私有的。 保留了优雅,方法可以在内部使用,而客户端的接口看起来像预期的那样。

重载等于的问题:

  • Java提供的所有集合即; Set,List,Map使用重写方法比较两个对象。 因此,即使重载equals方法,也无法解决比较两个对象的问题。 此外,如果您只是重载并实现哈希码方法,它将导致错误的行为

  • 如果您同时重载和重写equals方法并公开这两种方法,那么您将混淆客户端开发人员。 按照惯例,人们认为你重写了Object类

书中有许多内容涵盖了这一点。 (它不在我面前,所以我会在记住它们的时候引用物品)

有一个例子正好使用equals(..) ,据说不应该使用重载,如果使用 – 应该小心使用。 关于方法设计的项目警告不要使用相同数量的参数重载方法。 所以 – 不,不要超载equals(..)

更新:来自“Effective Java”(第44页)

只要两种方法返回相同的结果,就可以提供正常方法之外的这种“强类型”等方法,但没有令人信服的理由这样做。

因此,不禁止这样做,但它增加了你的课程的复杂性,同时没有增加收益。

我在我的项目中使用这种方法使用覆盖和重载组合,因为代码看起来更清晰。 到目前为止,我对这种方法没有任何问题。

我可以想到一个非常简单的例子,它将无法正常工作,为什么你永远不应该这样做:

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

在此测试中,您可以清楚地看到重载方法在使用inheritance时弊大于利。 在两种错误的情况下,调用更通用的equals(A a) ,因为Java编译器只知道a类型为A并且该对象没有重载的equals(B b)方法。

事后的想法 :让重载equals私有可以解决这个问题,但这真的让你获益吗? 它只添加一个额外的方法,只能通过执行转换来调用。