Заставить класс переопределить метод .equals

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

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

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

все в порядке.

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

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

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


person Antoine Claval    schedule 23.10.2009    source источник
comment
интерфейс Command {public abstract boolean equals (Object that); }   -  person Kanagavelu Sugumar    schedule 01.05.2021


Ответы (12)


Нет, не можешь. Однако вы можете использовать абстрактный базовый класс вместо интерфейса и сделать 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 ошибутся.

person skaffman    schedule 23.10.2009
comment
Вы можете добавить абстрактное hashCode, просто для полноты. - person McDowell; 23.10.2009
comment
работают как шарм, и действительно, плюс у меня уже был абстрактный базовый класс .... так что это небольшой рефакторинг. Спасибо - person Antoine Claval; 23.10.2009
comment
После более чем десяти лет использования Java в качестве первого языка я только что узнал кое-что новое. - person Andrew Duffy; 23.10.2009

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

public abstract class XObject
 extends Object
{
@Override
public abstract boolean equals(Object o);
}
person Pierre    schedule 23.10.2009
comment
Однако нет гарантии, что реализация Command расширит этот класс. - person skaffman; 23.10.2009
comment
Это было то, что я собирался опубликовать изначально :-) - person Tom Neyland; 23.10.2009
comment
Ах ... но вы могли бы объявить свои другие API-интерфейсы, требующие экземпляра (подкласса) вашего абстрактного класса, а не просто экземпляра Command. Немного уродливо, но если вы ДЕЙСТВИТЕЛЬНО хотите заставить людей отвергать равных, это сработает. - person Stephen C; 23.10.2009

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

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

person Philippe Gioseffi    schedule 12.04.2013

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

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

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

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

person NT_    schedule 23.10.2009
comment
Он работает только для абстрактных классов (как описано Пьером), но не для интерфейсов. Вам не нужно реализовывать метод equals, объявленный в интерфейсе, потому что он уже присутствует в вашем объекте. - person Jorn; 23.10.2009
comment
Прочтите мое второе предложение еще раз. - person NT_; 23.10.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)

person Nikita Prokopov    schedule 23.10.2009
comment
Это лучшее решение. Другой (используйте абстрактное равенство) не гарантирует, что абстракция останется в будущем. Если в определенный момент разработчик удалит две абстракции, результат сборки будет нормальным, но равенство больше не работает. Использование этого адаптера гарантирует, как минимум, ошибку компилятора, если кто-то изменит код. - person Rudy Barbieri; 14.05.2019

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

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

person Nikolay Ivanov    schedule 23.10.2009

Поскольку equals() унаследован от Object, я сомневаюсь, что вы действительно не можете заставить это, поскольку для каждого типа доступна автоматическая унаследованная реализация equals().

person Joey    schedule 23.10.2009

Я не думаю, что можно принудительно переопределить равные значения, поскольку это происходит из класса Object.

В соответствующей заметке обратите внимание, что вам нужно переопределить метод hashCode из класса Object, когда вы переопределяете equals. Это становится особенно важным, если вы собираетесь использовать экземпляры своих классов в качестве ключей Map. Проверьте эту статью: http://www.artima.com/lejava/articles/equality.html, который дает некоторые подсказки о том, как правильно переопределить равно

person sateesh    schedule 23.10.2009

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

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

public interface MappableCommand 
{

}

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

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

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

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

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

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

public interface MappableCommand 
{

    public void iOverrodeTheEqualsMethod();

    public void seriouslyIPromiseThatIOverrodeIt();

}
person Tom Neyland    schedule 23.10.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>();

Или, если вы действительно хотите быть уверенным, используйте проверенную хэш-карту; например

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

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

person Stephen C    schedule 23.10.2009
comment
Конечно, но если разработчик пропустил переопределение .equals. Будет бардак на нескольких картах. И это сложно понять. Я рискую. - person Antoine Claval; 23.10.2009

Можно ли добавить свой java.util.comparator к рассматриваемой карте?

person DerMike    schedule 27.10.2009

person    schedule
comment
поэтому C должен переопределить метод, используемый для equals. - person Davide Consonni; 23.10.2009