过载等于值得吗

2022-09-01 14:28:33

请考虑以下代码段:

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"
    }
}

请注意,返回!!!我们似乎失去了我们的东西!containsfalse

当然,bug 是因为我们意外地重载了 Object.equals(Object)而不是被覆盖。如果我们按如下方式编写,则按预期返回。class Thingcontainstrue

        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@Override equals(Thing other)

然而,这本书没有特别介绍的是,重载是否是一个好主意。基本上,有3种情况:equals

  • 只有过载,没有覆盖 - 几乎肯定是错误的
    • 这基本上是上面的第一个片段
  • 仅覆盖(无过载) - 修复的一种方法
    • 这基本上是上面的第二个片段
  • 重载和覆盖组合 - 另一种修复方法

第三种情况由以下代码段说明:

        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

答案 1

我不认为重载等于的情况,除了这更容易出错并且更难维护,尤其是在使用继承时。

在这里,保持自反性,对称性和传递性或检测它们的不一致可能非常困难,因为您必须始终了解被调用的实际等于方法。只要想想一个大型的继承层次结构,并且只有一些类型实现自己的重载方法。

所以我会说不要这样做。


答案 2

如果你有一个字段,如你的例子,我认为

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

所以我的经验法则基本上是,如果需要在仅覆盖中多次投射它,请执行覆盖/重载组合


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

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

通过使用重载/覆盖组合,编译器在某些情况下(不是全部!)将设法在没有向下转换的情况下完成。


为了评论@Snehal点,公开这两种方法可能会让客户端开发人员感到困惑:另一种选择是让重载的等式是私有的。保留了优雅,该方法可以在内部使用,而客户端的接口看起来如预期的那样。


推荐