🦘
Перечисления в публичном API
При использовании библиотеки Jackson для сериализации POJO в JSON перечисления вполне можно использовать в качестве поля такого POJO. Если при этом используются какие-либо генераторы openApi документации, то они автоматически подхватят это перечисление и сгенерируют корректное описание.
Если же нужно, чтобы в коде использовалось одно название перечисления, а в API другое (не капслоковая строка или даже целое число), то удобно пользоваться аннотацией @JsonValue
.
Ее нужно поставить над полем, в которое будет преобразовано перечисление при сериализации в JSON (и наоборот).
public enum Grade {
EXCELLENT('A'),
SATISFACTORY('B'),
MEDIOCRE('C'),
INSUFFICIENT('D'),
FAILURE('F');
@JsonValue
private final char mark;
}
Перечисления в БД
Многие СУБД позволяют создавать в таблицах колонки с перечисляемым типом. Но это влечет за собой некоторые проблемы:
- Добавление нового значения перечисления влечет за собой изменение схемы
- Liquibase не поддерживает перечисляемые типы при описании миграции с помощью xml. Из-за этого такие миграции приходится писать на SQL, что требует написания еще и down миграций.
Зато взамен вы получаете увеличенный перфоманс при фильтрации по этой колонке и существенно сокращаете размер индекса по этой колонке.
При использовании JPA можно над полем перечисляемого типа в сущности поставить аннотацию @Enumerated
.
@Enumerated(EnumType.STRING)
private Type type;
После этого обращаться с такими перечислениями нужно вдвойне осторожно, т.к. при изменении названия одного из значений, можно получить ошибки в рантайме при переводе ResultSet
в объект доменной области.
Получение перечисления по значению
Часто требуется получать перечисление по значению (строковому, целочисленному или др.).
Для этого рекомендуется создать в перечислении приватную мапу ‘значение к перечислению’.
Для перечисления ErrorCode
, базирующегося на строковом значении, это будет выглядеть так:
private static final Map<String, ErrorCode> VALUE_TO_ENUM = EnumSet.allOf(ErrorCode.class).stream()
.collect(Collectors.toMap(ErrorCode::getValue, Function.identity()));
public static ErrorCode byCount(String value) {
return Optional.ofNullable(VALUE_TO_ENUM.get(value))
.orElseThrow(() -> new IllegalArgumentException(String.format("Have no code for value '%s'", value)));
}
Коллекции с перечислениями
EnumSet
- шикарная коллекция, позволяющая объединять перечисления в группы и по ним выстраивать развилки в бизнес-логике.
Эта коллекция обладает потрясающей производительностью, а еще у нее шикарный API, позволяющий очень гибко создавать множество с необходимыми значениями.
EnumMap
- мапа, внутри которой значения укладываются в обычный массив.
У этой коллекции не самый удобный API, что ограничивает ее применение.
Про использование этих коллекций хорошо написано в книге Джошуа Блоха “Effective Java”.
В библиотеке Guava есть дополнительные коллекции, упрощающие работу с перечислениями.
Как писать тесты с перечислениями
В первую очередь, не стоит генерировать перечисление для тестовой DTO рандомно. Зачастую перечисления используются для разветвлений в бизнес-логике, поэтому при изменении этой самой бизнес-логики некоторые тесты могут начать рандомно падать. В результате можно получить ситуацию, когда локально тесты прошли, в CI они тоже прошли, а после мержа в master ветку CI падает, потому что зарандомилось неподходящее перечисление.
В JUnit для прогонки параметрического теста по значениям перечисления существует аннотация @EnumSource
.
К сожалению, ей становится неудобно пользоваться, когда необходимо исключить какие-то значения перечисления из теста.
Неудобство заключается в том, что конкретные значения перечисления передаются в виде строк, что может грозить ошибками при рефакторинге.
В таких случаях лучше воспользоваться аннотацией @MethodSource
, а необходимые значения перечисления генерировать с помощью EnumSet
.