У меня проблема с переопределением метода 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)
?
Вы можете НЕ @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[]
.
Вы можете решить эту проблему, вызвав свой метод hasSameCoordinatesAs или аналогичный, а не equals.
equals for enums определено в спецификации языка, поэтому вы не можете надеяться на его переопределение.
В Java это невозможно. (Единственная цель ключевого слова final, когда речь идет о методах, состоит в том, чтобы предотвратить переопределение!)
equals
и несколько других методов в Enums являются окончательными, поэтому вы не можете изменить их поведение. (И вы не должны :) Вот мой ответ на связанный вопрос:
Интуиция клиентов, которые имеют дело с константами перечисления, состоит в том, что две константы равны
тогда и только тогда, когда они являются одной и той же константой. Таким образом, любая другая реализация, кроме return this == other
, была бы нелогичной и подверженной ошибкам.
То же рассуждение применимо к hashCode()
, clone()
, compareTo(Object)
, name()
, ordinal()
и getDeclaringClass()
.
JLS не мотивирует сделать его окончательным, но упоминает равенство в контексте перечислений здесь. Фрагмент:
Метод equals в Enum — это последний метод, который просто вызывает super.equals для своего аргумента и возвращает результат, таким образом выполняя сравнение идентичности.
Равенство неуловимо. Разные контексты требуют разных отношений равенства. Имея метод equals() для Object, Java навязывает «внутреннее» равенство, и API, такие как Set, зависят от него.
В то же время упорядочение не считается «внутренним», два объекта могут быть упорядочены по-разному в разных контекстах, а API обычно позволяют нам предоставлять компратор, т. е. пользовательское отношение упорядочения.
Это интересно. С математической точки зрения, равенство, как и порядок, — это просто отношение, и могут быть разные отношения равенства. Концепция «внутреннего равенства» не свята.
Так что давайте также создадим Equal-ator и изменим API, чтобы они принимали пользовательские отношения равенства:
interface Equalator
boolean equal(a, b)
public HashSet( Equalator equalator )
На самом деле, мы можем создавать оболочки вокруг API текущей коллекции и добавлять эту функцию нового равенства.
Возможно, это ответ на ваш вопрос. Почему у вас вообще есть зависимость от equals()? И можете ли вы удалить это и вместо этого полагаться на «эквалатор»? Тогда вы настроены.