Как переопределить метод (final) equals в перечислениях java?

у меня есть проблема с переопределением метода equals в перечислении, чтобы сделать его совместимым с другими классами. Перечисление реализует интерфейс и идея заключается в том, что все реализации этого интерфейса могут быть проверены на равенство, независимо от их типа. Например:

public interface Group {
    public Point[] getCoordinates();
}

public enum BasicGroups implements Group {
    a,b,c; // simplified, they actually have constructors
    // + fields and methods
}

public class OtherGroup implements Group {
    // fields and methods
}

если оба a BasicGroup и OtherGroup имеют одинаковые координаты (в произвольном порядке), тогда метод equals должен возвращать true.

нет проблем при выполнении myOtherGroup.equals(BasicGroup.a) но с метод equals в перечислениях является окончательным, я не могу их переопределить.

есть ли способ обойти это? Например, при тестировании на другой BasicGroup метод по умолчанию равен (ссылка равенства) используется и при тестировании других классов используется моя собственная реализация. И как я могу убедиться, что java не использует неправильный, когда я делаю BasicGroup.a.equals(myOtherGroup)?

4 ответов


вы можете не @Override a final метод (§8.4.3.3); это очевидно. enum типа (§8.9) обрабатываются очень специально на Java, поэтому equals is final (кроме clone, hashCode, etc.) Это просто невозможно @Override на equals метод enum, и вы действительно не хотите в более типичном сценарии использования.

, глядя на общую картину, похоже, что вы попытка следовать шаблону, рекомендованному в эффективное Java 2nd Edition, пункт 34: эмулировать расширяемые перечисления с интерфейсами (см. руководство по языку подробнее о enum):

вы определили это interface (теперь задокументировано явно для expected equals поведения):

public interface Group implements Group {
    public Point[] getCoordinates();

    /*
     * Compares the specified object with this Group for equality. Returns true
     * if and only if the specified object is also a Group with exactly the same
     * coordinates
     */
    @Override public boolean equals(Object o);
}

это вполне приемлемо для interface определить как equals метод для реализаторов должен вести себя, конечно. Это точно случае с, например,List.equals. Пустой LinkedList и equals пустой ArrayList и наоборот, потому что interface мандатов.

в вашем случае вы решили реализовать некоторые Group as enum. К сожалению, теперь вы не можете реализовать equals согласно спецификации, так как это final и вы не можете @Override его. Однако, поскольку цель состоит в том, чтобы соответствовать Group тип, вы можете использовать шаблон "декоратор" имея ForwardingGroup следующим образом:

public class ForwardingGroup implements Group {
   final Group delegate;
   public ForwardingGroup(Group delegate) { this.delegate = delegate; }

   @Override public Point[] getCoordinates() {
       return delegate.getCoordinates();
   }
   @Override public boolean equals(Object o) {
       return ....; // insert your equals logic here!
   }
}

теперь, вместо enum константы непосредственно в качестве Group, вы wrap их в экземпляре ForwardingGroup. Теперь это!--24--> объект будет иметь желаемое equals поведение, как указано в interface.

то есть, вместо:

// before: using enum directly, equals doesn't behave as expected
Group g = BasicGroup.A;

теперь у вас есть что-то вроде:

// after: using decorated enum constants for proper equals behavior
Group g = new ForwardingGroup(BasicGroup.A);

дополнительная информация

тот факт, что enum BasicGroups implements Group, даже хотя он сам не следует спецификации Group.equals, должно быть очень четко документированы. Пользователи должны быть предупреждены, что константы должны быть, например, обернуты внутри ForwardingGroup на надлежащего equals поведение.

Отметим также, что вы можете кэшировать экземпляры ForwardingGroup для каждого enum константы. Это поможет сократить количество создаваемых объектов. Согласно эффективное издание Java 2nd, пункт 1: рассмотрите статические заводские методы вместо конструкторы, вы можете рассмотреть ForwardingGroup определение static getInstance(Group g) метод вместо конструктора, позволяющий возвращать кэшированные экземпляры.

я предполагаю, что Group является неизменяемым типом (эффективное Java 2nd Edition, пункт 15: минимизировать изменчивость), или вы, вероятно, не должны реализовывать его с помощью enum в первую очередь. Учитывая это, подумайте эффективное Java 2nd Edition, пункт 25: предпочитайте списки массивам. Вы можете выбрать для getCoordinates() возвратить List<Point> вместо Point[]. Вы можете использовать Collections.unmodifiableList (еще один декоратор!), который возвращен List незыблемыми. Напротив, поскольку массивы изменчивы, вы будете вынуждены выполнять защитное копирование при возврате Point[].

см. также


это невозможно сделать на Java. (Единственная цель последнего ключевого слова, когда дело доходит до методов, - предотвратить переопределение!)

equals и несколько других методов перечислений являются окончательными, поэтому вы не можете изменить их поведение. (А ты!--12-->не стоит :) вот мой ответ вопрос:


интуиция клиентов, которые имеют дело с константами перечисления, заключается в том, что две константы equal если и только если они та же константа. Таким образом, любая другая реализация, чем return this == other было бы нелогично и подвержен ошибкам.

то же самое рассуждение относится к hashCode(), clone(), compareTo(Object), name(), ordinal() и getDeclaringClass().

JLS не мотивирует выбор сделать его окончательным, но упоминает равных в контексте перечислений здесь. Фрагмент:

метод equals в перечислении является окончательным методом, который просто вызывает super.equals по аргументу и возвращает результат, таким образом, выполняя сравнение идентичности.


вы можете решить эту проблему, вызвав свой метод hasSameCoordinatesAs или аналогичный, а не equals.

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


равенство довольно неуловимо. Различные контексты требуют различных отношений равенства. Имея метод equals () на объекте, Java накладывает "внутреннее" равенство, и API, как Set, зависят от него.

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

Это интересно. В математических терминах равенство, как и порядок, просто отношения, и могут быть разные отношения равенства. Понятие "внутреннего равенства" не Свято.

Итак, давайте тоже будем иметь Equal-ator и изменим API, чтобы принять пользовательские отношения равенства:

interface Equalator
    boolean equal(a, b)

public HashSet( Equalator equalator )

фактически, мы можем создавать обертки вокруг текущих API коллекции и добавлять эту функцию нового равенства.

Это может ответить на ваш вопрос. Почему у вас есть зависимость от equals () в первую очередь? И можете ли вы удалить это и зависеть вместо этого от "equalator"? Тогда вы готовы.