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

时间:2010-05-26 06:23:35

标签: java equals overloading override

请考虑以下代码段:

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

8 个答案:

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