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

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

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
}

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

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

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

14
задан neXus 25 August 2010 в 08:40
поделиться

4 ответа

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

ОДНАКО, глядя на общую картину, кажется, что вы пытаетесь следовать шаблону, рекомендованному в Effective Java 2nd Edition, Item 34: Emulate extensible enums with interfaces (см. language guide для получения дополнительной информации о enum):

Вы определили этот интерфейс (теперь задокументировано явно для ожидаемого поведения 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);
}

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

В вашем случае вы решили реализовать некоторую Группу как 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, вы оборачиваете их в экземпляр ForwardingGroup. Теперь этот объект Group будет иметь желаемое поведение equals, как указано в интерфейсе.

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

// 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 реализует Группу, хотя сама она не соответствует спецификации Group.equals, должна быть очень четко задокументирована. Пользователи должны быть предупреждены, что константы должны быть, например. завернутый внутрь ForwardingGroup для правильного поведения equals.

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

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

См. также

17
ответ дан 1 December 2019 в 09:11
поделиться

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

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

2
ответ дан 1 December 2019 в 09:11
поделиться

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

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


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

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

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

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

8
ответ дан 1 December 2019 в 09:11
поделиться

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

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

Это интересно. С математической точки зрения, равенство, как и порядок, — это просто отношение, и могут быть разные отношения равенства. Концепция «внутреннего равенства» не свята.

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

interface Equalator
    boolean equal(a, b)

public HashSet( Equalator equalator )

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

Возможно, это ответ на ваш вопрос. Почему у вас вообще есть зависимость от equals()? И можете ли вы удалить это и вместо этого полагаться на «эквалатор»? Тогда вы настроены.

2
ответ дан 1 December 2019 в 09:11
поделиться
Другие вопросы по тегам:

Похожие вопросы: