Как переопределить (окончательный) метод equals в java-перечислениях? - PullRequest
14 голосов
/ 25 августа 2010

У меня проблема с переопределением метода 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
}

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

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

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

Ответы [ 4 ]

17 голосов
/ 25 августа 2010

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

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

Вы определили это interface (теперь задокументировано явно для ожидаемого 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 как 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, как указано 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 константу. Это поможет уменьшить количество создаваемых объектов. Согласно Effective Java 2nd Edition, пункт 1: рассмотрите статические фабричные методы вместо конструкторов , вы можете рассмотреть возможность использования ForwardingGroup определения static getInstance(Group g) метода вместо конструктора, что позволяет ему возвращать кэшированные экземпляры.

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

Смотри также

8 голосов
/ 25 августа 2010

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

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


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

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

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

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

2 голосов
/ 25 августа 2010

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

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

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

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

interface Equalator
    boolean equal(a, b)

public HashSet( Equalator equalator )

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

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

2 голосов
/ 25 августа 2010

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...