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

pol*_*nts 26 java overriding overloading equals

请考虑以下代码段:

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"
    }
}
Run Code Online (Sandbox Code Playgroud)

注意contains返回false!!! 我们似乎失去了我们的东西!

这个bug,当然是事实,我们不小心过载,而不是,重写,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);
            }
        }
Run Code Online (Sandbox Code Playgroud)

有效的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));
            }
        }
Run Code Online (Sandbox Code Playgroud)

在这里,即使我们现在有2个equals方法,仍然有一个相等逻辑,它位于重载中.在@Override简单地委托给过载.

所以问题是:

  • "仅覆盖"vs"重载和覆盖组合"的优缺点是什么?
  • 是否存在超载的理由equals,或者这几乎肯定是一种不好的做法?

b_e*_*erb 17

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

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

所以我会说不要这样做.

  • 我强烈**不同意,因为被覆盖的等于将是自反的,传递的和对称的,**当且仅当**超载的等于. (2认同)

aio*_*obe 8

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

@Override public boolean equals(Object o) {
    return (o instanceof Thing) && (this.x == ((Thing) o).x);
}
Run Code Online (Sandbox Code Playgroud)

是要走的路.任何其他东西都会过于复杂.但是如果你添加一个字段(并且不想通过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;
}
Run Code Online (Sandbox Code Playgroud)

我认为这比稍微有些丑陋

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);
}
Run Code Online (Sandbox Code Playgroud)

所以我的经验法则基本上是,如果需要在覆盖中多次投射它,请执行override-/overload-combo.


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

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

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


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