Хорошая ли практика использовать порядковый номер enum?
У меня есть перечисление (enum):
public enum Persons {
CHILD,
PARENT,
GRANDPARENT;
}
Есть ли какие-либо проблемы с использованием метода ordinal()
для проверки "иерархии" между членами перечисления? Я имею в виду - есть ли какие-то недостатки при использовании этого метода, кроме избыточности, когда кто-то может случайно изменить порядок в будущем?
Или лучше сделать что-то подобное:
public enum Persons {
CHILD(0),
PARENT(1),
GRANDPARENT(2);
private Integer hierarchy;
private Persons(final Integer hierarchy) {
this.hierarchy = hierarchy;
}
public Integer getHierarchy() {
return hierarchy;
}
}
Какие подходы вы бы порекомендовали в данном случае?
5 ответ(ов)
TLDR: Нет, не стоит этого делать!
Если вы обратитесь к javadoc для метода ordinal
в Enum.java
, то найдете следующее:
Большинство программистов не будут использовать этот метод. Он предназначен для использования в сложных структурах данных на основе перечислений, таких как
java.util.EnumSet
иjava.util.EnumMap
.
Во-первых, прочитайте документацию (в данном случае javadoc).
Во-вторых, не пишите хрупкий код. Значения перечисления могут измениться в будущем, и ваш второй пример кода гораздо более понятен и поддерживаем.
Вы определенно не хотите создавать проблемы в будущем, если, скажем, новое значение перечисления будет вставлено между PARENT
и GRANDPARENT
.
Как предложил Джошуа Блох в книге Effective Java, не стоит извлекать значение, связанное с перечислением (enum), из его порядкового номера (ordinal), так как изменения в порядке значений перечисления могут нарушить заложенную вами логику.
Второй подход, который вы упоминаете, точно соответствует предложению автора, заключающемуся в том, чтобы хранить значение в отдельном поле.
Я бы сказал, что предложенная вами альтернатива определенно лучше, так как она более расширяема и удобна в поддержке. Вы отделяете порядок значений перечисления от понятия иерархии.
Первый способ не совсем очевиден, так как вам нужно читать код, в котором используются перечисления (enums), чтобы понять, что порядок значений перечисления имеет значение. Это очень подвержено ошибкам.
public enum Persons {
CHILD,
PARENT,
GRANDPARENT;
}
Второй способ лучше, так как он самообъясняем:
CHILD(0),
PARENT(1),
GRANDPARENT(2);
private SourceType(final Integer hierarchy) {
this.hierarchy = hierarchy;
}
Разумеется, порядок значений перечисления должен соответствовать иерархическому порядку, указанному в аргументах конструктора перечисления.
Это вводит некую избыточность, так как и значения перечисления, и аргументы конструктора перечисления передают информацию об их иерархии.
Но почему это может быть проблемой?
Перечисления предназначены для представления констант и значений, которые не меняются часто.
Пример использования перечисления в вопросе хорошо иллюстрирует правильное использование перечислений:
CHILD, PARENT, GRANDPARENT
Перечисления не предназначены для представления значений, которые часто изменяются. В этом случае использование перечислений, вероятно, не является лучшим выбором, так как это может часто ломать клиентский код, который их использует, и, кроме того, это требует повторной компиляции, упаковывания и развертывания приложения каждый раз, когда изменяется значение перечисления.
Если вам нужно создать отношения между значениями перечисления (enum), можно использовать следующий прием с другими значениями перечисления:
public enum Person {
GRANDPARENT(null),
PARENT(GRANDPARENT),
CHILD(PARENT);
private final Person parent;
private Person(Person parent) {
this.parent = parent;
}
public final Person getParent() {
return parent;
}
}
Обратите внимание, что вы можете использовать только те значения перечисления, которые были объявлены в коде раньше, чем то, которое вы пытаетесь объявить. Это означает, что такой подход будет работать только в тех случаях, когда ваши отношения образуют ациклический ориентированный граф (и порядок их объявления соответствует корректной топологической сортировке).
Использование ordinal()
не рекомендуется, так как изменения в объявлении перечисления могут повлиять на значения порядковых номеров.
ОбНОВЛЕНИЕ:
Стоит отметить, что поля перечисления являются константами и могут иметь дублирующиеся значения, например:
enum Family {
OFFSPRING(0),
PARENT(1),
GRANDPARENT(2),
SIBLING(3),
COUSING(4),
UNCLE(4),
AUNT(4);
private final int hierarchy;
private Family(int hierarchy) {
this.hierarchy = hierarchy;
}
public int getHierarchy() {
return hierarchy;
}
}
В зависимости от того, что вы собираетесь делать с hierarchy
, это может быть как вредным, так и полезным.
Более того, вы можете использовать константы перечисления для создания своих собственных EnumFlags
, вместо использования EnumSet
, например:
Сравнение членов enum в Java: использовать == или equals()?
Можно ли наследовать перечисления для добавления новых элементов?
Jackson: Сериализация и десериализация значений enum в виде целых чисел
Являются ли имена перечислений (enum) в Java интернированными?
JavaDoc: где добавлять заметки/пояснения в документацию?