关于平等的最佳做法:过载还是不过载?
请考虑以下代码段:
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
私有可以解决这个问题,但这真的让你获益吗? 它只添加一个额外的方法,只能通过执行转换来调用。