Заставить класс переопределить метод .equals - PullRequest
51 голосов
/ 23 октября 2009

У меня есть группа классов, которые реализуют общий интерфейс: Command.

И эта группа классов идет на Карту.

Чтобы карта работала правильно, мне нужно, чтобы каждый класс, реализующий Command, переопределил метод Object.equals(Object other).

все хорошо.

Но я бы хотел форсировать равных. => Ошибка компиляции, когда что-то, кто реализует команду dont override, равно.

Это возможно?

Редактировать: Кстати, мне также нужно принудительно переопределить хэш-код ...

Ответы [ 12 ]

81 голосов
/ 23 октября 2009

Нет, вы не можете. Однако вы можете использовать абстрактный базовый класс вместо интерфейса и сделать equals() abstract:

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 абстрактный базовый класс вместо интерфейса, вместо того, чтобы вводить искусственный базовый класс в добавление к интерфейсу Command, тогда ваши пользователи API не рискуют ошибиться. 1013 *

17 голосов
/ 23 октября 2009

Можете ли вы расширить свои объекты из абстрактного XObject вместо java.lang.Object?

public abstract class XObject
 extends Object
{
@Override
public abstract boolean equals(Object o);
}
4 голосов
/ 12 апреля 2013

Абстрактные классы не будут работать, если у вас есть внук, поскольку его отец уже переопределил методы equals и hashCode, и тогда у вас снова возникнет проблема.

Попробуйте использовать аннотатины и APT (http://docs.oracle.com/javase/1.5.0/docs/guide/apt/GettingStarted.html), чтобы сделать это.

1 голос
/ 23 октября 2009

Ну, если вам нужна проверка во время выполнения, вы можете сделать что-то вроде:

    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 для второго. Если вы хотите, чтобы время компиляции выглядело как единственный вариант, это создать аннотацию и использовать инструмент обработки аннотаций, чтобы проверить, что все аннотированные классы переопределяют равные.

1 голос
/ 23 октября 2009
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.");
    }
}
1 голос
/ 23 октября 2009

Вы можете создать boolean myEquals() в interface Command и создать адаптер следующим образом:

class MyAdapter{
  Command c;
  boolean equals(Object x) {
    return c.myEquals((Command)x);
  }
}

Тогда вы просто используете map.put(key, new MyAdapter(command)) вместо map.put(key, command)

1 голос
/ 23 октября 2009

Это было бы возможно только в том случае, если Command был интерфейсом или абстрактным классом, где метод equals (..) объявлен как абстрактный.

Проблема в том, что Object, который является суперклассом всех объектов, уже определяет этот метод.

Если вы хотите указать, что это проблема (во время выполнения), вы можете вызвать исключение, чтобы заставить пользователей вашего API переопределить его. Но это невозможно во время компиляции, по крайней мере, насколько мне известно.

Попробуйте обойти это, используя специфичный для API метод, например, CommandEquals. Другим вариантом является (как уже упоминалось) расширение другого класса, который определяет абстрактный метод Equals.

0 голосов
/ 27 октября 2009

Можно ли предоставить свою собственную java.util.comparator на карту, о которой идет речь?

0 голосов
/ 23 октября 2009

Вот вариант некоторых других предложенных решений:

public abstract class CommandOverridingEquals implements Command {
    public abstract boolean equals(Object other);
    public abstract int hashcode();
}

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

Или, если вы действительно хотите быть уверенным, используйте проверенный hashmap; например,

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(); }
    ...
}

Я склонен думать, что подобные вещи вызовут проблемы в будущем. Например, предположим, что у меня есть набор существующих классов команд, которые расширяют некоторый другой базовый класс, и (между прочим) переопределяют equals и hashcode предписанным способом. Проблема в том, что я не могу использовать эти классы. Вместо этого я вынужден переопределить их или написать связку оберток.

IMO, плохая идея пытаться заставить разработчика использовать конкретный шаблон реализации. Было бы лучше поместить несколько сильных предупреждений в Javadocs и положиться на разработчиков, которые поступят правильно .

0 голосов
/ 23 октября 2009

Как уже объясняли другие ответы, нет, вы не можете заставить то, что пытаетесь сделать.

Одна вещь, которая может работать «достаточно», - это определить второй интерфейс, назовите этот MappableCommand.

public interface MappableCommand 
{

}

В вашей документации по этому интерфейсу укажите, что класс должен реализовывать этот (пустой) интерфейс только в том случае, если разработчик класса учел указанные вами требования.

Затем вы можете установить тип значения вашей карты на MappableCommand, и только MappableCommands смогут быть добавлены на карту.

Это похоже на логику того, почему нужно реализовать (пустой) интерфейс Serializable для классов, которые можно сериализовать с помощью механизмов сериализации Java по умолчанию.

Если это не сработает, возможно, вам придется согласиться на выдачу ошибки во время выполнения;

Поддельный Редактировать:

Если вы хотите сделать это требование более очевидным, вы можете определить новый интерфейс следующим образом

public interface MappableCommand 
{

    public void iOverrodeTheEqualsMethod();

    public void seriouslyIPromiseThatIOverrodeIt();

}
...