强制一个类重写.equals方法

57

我有一堆实现同一个接口Command的类。

这些类被放置在Map中。

为了让Map正常工作,我需要让每个实现Command接口的类重写Object.equals(Object other)方法。

这是没问题的。

但是我想强制要求重写equals方法 => 当某个实现Command的类没有重写equals时,编译出错。

这可能吗?

编辑:顺便说一下,我还需要强制重写hashcode...


接口命令 { public abstract boolean equals(Object that); } - Kanagavelu Sugumar
12个回答

90

不行,你不能这样做。但是,你可以使用抽象基类代替接口,并将equals()声明为抽象方法:

abstract class Command {
   // put other methods from Command interface here

   public abstract boolean equals(Object other);
   public abstract int hashCode();
}

Command的子类必须提供自己的equals和hashCode方法。

通常强制API用户扩展基类是不好的做法,但在这种情况下可能是合理的。此外,如果将Command作为抽象基类而不是接口,则不会引入人工基类以外的风险,并且不需要让API用户担心是否使用正确。


18
为了完整性,您可能希望添加一个摘要 hashCode - McDowell
工作得很好,而且我已经有了一个抽象基类……所以,这不需要进行大规模的重构。谢谢。 - Antoine Claval
6
作为我的第一门语言十多年后,我刚学到了一些新的Java知识。 - Andrew Duffy

17

你能够将对象从抽象的XObject扩展而来,而不是从java.lang.Object扩展吗?

public abstract class XObject
 extends Object
{
@Override
public abstract boolean equals(Object o);
}

1
虽然不能保证Command的实现会扩展此类。 - skaffman
这就是我原本要发布的内容 :-) - Tom Neyland
1
啊...但你可以声明你的其他API需要一个(子类的)抽象类实例,而不仅仅是一个Command实例。这有点丑陋,但如果你真的想强制人们覆盖equals方法,那就能达到目的。 - Stephen C

4

1
你可以在接口 Command 中创建 boolean myEquals(),并创建适配器如下所示:
class MyAdapter{
  Command c;
  boolean equals(Object x) {
    return c.myEquals((Command)x);
  }
}

那么你只需要使用 map.put(key, new MyAdapter(command)) 而不是 map.put(key, command)

这是最好的解决方案。另一个(使用抽象等于)不能保证抽象将来仍然存在。如果在某个时刻开发人员删除了两个抽象,构建结果是可以的,但等于不再起作用。使用此适配器至少确保如果有人更改代码,则会出现编译器错误。 - Rudy Barbieri

1
interface A{
    public boolean equal2(Object obj);
}

abstract class B implements A {

    @Override
    public boolean equals(Object obj) {
        return equal2(obj);
    }

}


class C extends B {

    public boolean equal2(Object obj) {
        throw new UnsupportedOperationException("Not supported yet.");
    }
}

因此,C必须覆盖用于equals的方法。 - Davide Consonni

1

如果你想要一个运行时检查,你可以这样做:

    interface Foo{

}
class A implements Foo {

}
class B implements Foo {
    @Override
    public boolean equals(Object obj) {
        return super.equals(obj);
    }
}

public static void main(String[] args) {
    Class<A> clazzA = A.class;
    Class<B> clazzB = B.class;

    Class<Object> objectClass = Object.class;
    try {
        Method methodFromObject = objectClass.getMethod("equals",Object.class);
        Method methodFromA = clazzA.getMethod("equals",Object.class);
        Method methodFromB = clazzB.getMethod("equals",Object.class);
        System.out.println("Object == A" + methodFromObject.equals(methodFromA));
        System.out.println("Object == B" + methodFromObject.equals(methodFromB));
    } catch (SecurityException e) {
        e.printStackTrace();
    } catch (NoSuchMethodException e) {
        e.printStackTrace();
    }
}

第一个会打印true,第二个会打印false。 如果你想在编译时检查所有带注解的类是否重写了equals方法,看起来唯一的选择是创建一个注解并使用注解处理工具。


1

只有在Command是一个接口或抽象类的情况下,才能实现equals(..)作为抽象方法的声明。

问题在于,所有对象的超类Object已经定义了这个方法。

如果您希望指示这是一个问题(在运行时),可以抛出异常,强制API用户覆盖它。但至少在我所知道的范围内,这在编译时不可能。

尝试通过具有特定于API的方法(例如CommandEquals)来解决它。另一种选择是(如上所述)扩展另一个定义了Equals抽象方法的类。


它仅适用于抽象类(如Pierre所述),而不适用于接口。您无需实现接口中声明的equals方法,因为它已经存在于您的对象中。 - Jorn
请再读一遍我的第二句话。 - NT_

0

正如其他答案已经解释过的那样,你不能强制执行你正在尝试的那种事情。

可能有效的一件事是定义第二个接口,称之为MappableCommand。

public interface MappableCommand 
{

}

在您的接口文档中,请指明只有在类设计者考虑了您所述的要求时,才应该实现此(空)接口。
然后,您可以将地图的值类型设置为MappableCommand,只有MappableCommands才能添加到地图中。
这类似于为可以通过Java的默认序列化机制进行序列化的类实现(空白)接口Serializable背后的逻辑。
如果这不起作用,那么您可能不得不选择抛出运行时错误;
虚假编辑:
如果您想使此要求更加明显,可以按以下方式定义新接口。
public interface MappableCommand 
{

    public void iOverrodeTheEqualsMethod();

    public void seriouslyIPromiseThatIOverrodeIt();

}

0
这是其他一些提出的解决方案的变体:
public abstract class CommandOverridingEquals implements Command {
    public abstract boolean equals(Object other);
    public abstract int hashcode();
}

Map<String, CommandOverridingEquals> map = 
    new HashMap<String, CommandOverridingEquals>();

或者如果你真的想确保,可以使用一个带检查的哈希表;例如:

Map<String, CommandOverridingEquals> map = Collections.checkedMap(
    new HashMap<String, Command>(),
    String.class, CommandOverridingEquals.class);

但是无论你做什么,都无法阻止有人这样做:

public class AntiFascistCommand extends CommandOverridingEquals {
    public boolean equals(Object other) { return super.equals(other); }
    public int hashcode() { return super.hashcode(); }
    ...
}

我倾向于认为这种做法会在未来造成麻烦。例如,假设我有一堆现有的命令类,它们扩展了某个其他基类,并且(顺便说一下)按照规定的方式覆盖了equalshashcode。问题是,我不能使用这些类。相反,我被迫重新实现它们或编写一堆包装器。

在我看来,试图强制开发人员采用特定的实现模式是一个坏主意。更好的做法是在Javadocs中放置一些强烈的警告,并依靠开发人员去“做正确的事情”。


当然可以,但是如果开发者错过了.equals重载,那么在几个映射中将会出现混乱,而且很难弄清楚。我冒这个险。 - Antoine Claval

0

由于equals()是从Object继承而来的,我怀疑你无法真正强制执行它,因为对于每个类型,都有一个自动继承的equals()实现可用。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接