Давайте посмотрим на мощь и красоту того, что может предоставить Java Enums…
Я также создал видео, описывающее те же темы, на основе этой статьи, так что вы можете продолжить, объединив видео и этот пост.
Что такое перечисление?
Перечисления — предпочтительный способ определения фиксированных значений, которые вы хотите использовать в своем коде. Они представляют собой особый тип класса Java и содержат группу неизменяемых переменных.
Например:
enum Level {
INFO,
WARNING,
ERROR
}
Проекты, использующие перечисления
Некоторые из моих «любимых проектов» широко используют перечисления Java, и, работая над ними, я понял, что не всем понятно, что они действительно мощные и могут содержать гораздо больше, чем просто список фиксированных значений.
Давайте рассмотрим несколько таких вариантов использования.



Платы Raspberry Pi в базе данных Enum
Для проекта Pi4J требовалась библиотека, содержащая некоторую информацию о различных платах Raspberry Pi и о том, какие протоколы они поддерживают на своих контактах GPIO (ввод/вывод общего назначения). Поскольку эта информация должна быть доступна внутри кода, на досках, работающих без подключения к Интернету, API не был правильным решением.
Поэтому было принято решение сгенерировать библиотеку Java, содержащую всю эту информацию, используя список перечислений: github.com/Pi4J/pi4j-board-info.
Поверх этой библиотеки создается веб-сайт и API по адресу api.pi4j.com. В результате базу данных теперь можно использовать в автономном и онлайн-режиме.
Парсинг LottieFiles
В другом проекте Lottie4J я пытаюсь разобрать анимацию в формате JSON в объекты Java для создания проигрывателя JavaFX. Чтобы упростить понимание формата LottieFiles в рамках этого Java-проекта, перечисления используются для различных определений, которые вы можете видеть в проекте GitHub.
Категории видео для 4drums.media
Этот проект вдохновлен моим 12-летним сыном, который хочет собирать и делиться барабанными видео и надеется, что сможет создать вокруг этого сообщество. Приятно иметь возможность использовать свои «профессиональные знания» для быстрого создания такого проекта благодаря Spring и Vaadin, но работа еще не завершена, так как мой сын создал список TODO с идеями из 100 оставшихся пунктов…
Перечисления используются в этом проекте для определения доступных категорий видео и руководств, которые включают метку, значок, цвет текста и фона.
public enum VideoCategory {
SPECTACULAR("la-dragon", "category.video.spectacular", "#CD0000", "#FFFFFF"),
TUTORIAL("la-drum", "category.video.tutorial", " #0000EE", "#FFFFFF"),
I_MADE_THIS("la-portrait", "category.video.selfmade", " #A2CD5A", "#000000"),
...;
private final String icon;
private final String translationId;
private final String colorBg;
private final String colorText;
VideoCategory(String icon, String translationId, String colorBg, String colorText) {
this.icon = icon;
this.translationId = translationId;
this.colorBg = colorBg;
this.colorText = colorText;
}
public String getIcon() {
return icon;
}
...
}
Примеры использования перечисления
Давайте рассмотрим несколько примеров, шаг за шагом. Полный код доступен на GitHub как EnumExtended.java и может быть выполнен с помощью JBang! с jbang EnumExtended.java.
Базовая функциональность
Основным примером использования Enum является следующее:
enum Level {
INFO,
WARNING,
ERROR
}
System.out.println("Error level: " + Level.WARNING);
Выход:
Error level: WARNING
Перечисления были введены для замены использования констант int, как это видно в других языках, которые выглядели бы так в Java:
class MyProgram {
public final static int LEVEL_INFO = 1;
public final static int LEVEL_WARNING = 2;
public final static int LEVEL_ERROR = 3;
...
}
Но enum может сделать намного больше! Давайте посмотрим на некоторые примеры…
Использование перечислений в Switch
Используя перечисления, код switch - case может быть написан очень чисто.
public int getLevelValue(Level level) {
switch (level) {
case INFO:
return 1;
case WARNING:
return 2;
case ERROR:
return 3;
}
return 0;
}
Но, как мы увидим дальше, этот код все еще можно значительно улучшить, расширив перечисление Level…
Используйте Enum вместо Boolean
Перечисления также являются хорошим вариантом использования для замены логических проверок. Возьмем пример с клиентами, которые могут стать «неактивными» в системе. Обычно вы делаете это с помощью логического значения.
class Customer {
String name;
boolean active;
...
}
Но чуть позже ваш вариант использования изменится, и клиенты также могут быть заблокированы, потому что они перестали платить по счетам. Не могли бы вы добавить еще boolean suspended? Это может привести к большому количеству изменений в вашей программе, которых можно избежать, используя перечисление для состояния клиента, например:
enum CustomerState {
ACTIVE,
INACTIVE,
SUSPENDED
}
class Customer {
String name;
CustomerState state;
...
}
Дополнительные данные в значении перечисления
Для меня истинная сила перечисления заключается в том, что оно может содержать много информации и предоставлять методы для использования этих значений.
В следующем примере перечисление Level, которое мы использовали ранее, расширено значением серьезности, описанием метки и цветовым кодом.
enum Level {
INFO(1, "Informative message", 0x00aa00),
WARNING(2, "Warning message", 0xFFA500),
ERROR(3, "Error message", 0xA30000);
private final int severity;
private final String label;
private final int color;
private Level(int severity, String label, int color) {
this.severity = severity;
this.label = label;
this.color = color;
}
public int getSeverity() {
return severity;
}
public String getLabel() {
return label;
}
public int getColor() {
return color;
}
}
Теперь мы можем получить данные всех значений уровня с помощью следующего кода:
for (Level level : Level.values()) {
System.out.println("Level " + level.name()
+ "\n\tSeverity: " + level.getSeverity()
+ "\n\tLabel: " + level.getLabel()
+ "\n\tColor: " + level.getColor());
}
Что будет производить этот вывод:
Level INFO Severity: 1 Label: Informative message Color: 43520 Level WARNING Severity: 2 Label: Warning message Color: 16753920 Level ERROR Severity: 3 Label: Error message Color: 10682368
Таким образом, это означает, что level.getSeverity() полностью заменяет метод getLevelValue(Level level) на switch-case, который мы видели раньше.
Разбор JSON с Enums
Во многих случаях, когда вы хотите сгенерировать выходные данные JSON или проанализировать JSON для объектов Java, могут быть задействованы перечисления.
Пример данных JSON
Возьмем, к примеру, этот файл JSON, который содержит несколько сообщений журнала. Уровень сохраняется со значением severity из перечисления Level.
[
{
"level": 1,
"timestamp": 1675867184342,
"message": "Program started"
},
{
"level": 2,
"timestamp": 1675867185921,
"message": "File X not found"
},
{
"level": 3,
"timestamp": 1675867186357,
"message": "Error at line Y"
}
]
Запись для загрузки данных JSON
Мы хотим прочитать эти сообщения в объекты Java, используя библиотеку Джексона и запись:
record LogMessage(Level level, Long timestamp, String message) {
@JsonIgnore
public ZonedDateTime getZonedDateTime() {
return ZonedDateTime.ofInstant(Instant.ofEpochMilli(timestamp()), ZoneId.of("UTC"));
}
}
Дополнительный метод getZonedDateTime преобразует значение Long timestamp для использования в нашем коде, но из-за атрибута @JsonIgnore это значение не будет преобразовано обратно в JSON, как вы увидите позже.
Чтение объектов JSON в Java
Используя библиотеку Джексона, мы можем легко преобразовать эти данные JSON в массив LogMessage, если мы добавим несколько атрибутов Джексона в перечисление и с помощью ObjectMapper:
enum Level {
...;
@JsonValue
private final int severity;
...
@JsonCreator
public static Level fromValue(Integer severity)
throws IllegalArgumentException {
return Arrays.stream(Level.values()).sequential()
.filter(v -> v.getSeverity() == severity)
.findFirst()
.orElseThrow(() ->
new IllegalArgumentException("The given severity does not exist:" + severity));
}
}
ObjectMapper objectMapper = new ObjectMapper();
LogMessage[] logMessages = objectMapper
.readValue(json, LogMessage[].class);
for (LogMessage logMessage : logMessages) {
System.out.println("Log message at "
+ logMessage.getZonedDateTime()
+ "\n\tSeverity: "
+ logMessage.level().getLabel()
+ "\n\tMessage: "
+ logMessage.message());
}
Это приведет к следующему выводу. Как видите, числовое значение уровня из JSON было преобразовано в значение Level-enum:
Log message at 2023-02-08T14:39:44.342Z[UTC]
Severity: Informative message
Message: Program started
Log message at 2023-02-08T14:39:45.921Z[UTC]
Severity: Warning message
Message: File X not found
Log message at 2023-02-08T14:39:46.357Z[UTC]
Severity: Error message
Message: Error at line Y
Преобразование объекта Java в данные JSON
Поскольку в предыдущих изменениях мы уже добавили атрибут @JsonValue к переменной severity перечисления, ObjectMapper будет использовать правильное значение для преобразования уровня обратно в JSON. Давайте используем PrettyPrinter для создания форматированного JSON из нашего массива logMessages:
System.out.println(objectMapper
.writerWithDefaultPrettyPrinter()
.writeValueAsString(logMessages));
Это создаст следующий вывод, который содержит то же содержимое, что и наши исходные данные JSON.
[ {
"level" : 1,
"timestamp" : 1675867184342,
"message" : "Program started"
}, {
"level" : 2,
"timestamp" : 1675867185921,
"message" : "File X not found"
}, {
"level" : 3,
"timestamp" : 1675867186357,
"message" : "Error at line Y"
} ]
Преобразование ZonedDateTime в JSON
Как упоминалось ранее, из-за атрибута @JsonIgnore метода getZonedDateTime это значение не включается в сгенерированный JSON. И это даже нуждается в дополнительном замечании... java.time.ZonedDateTime по умолчанию не поддерживается Джексоном. Необходим дополнительный модуль com.fasterxml.jackson.datatype:jackson-datatype-jsr310.
Таким образом, необходимо внести несколько дополнительных изменений, чтобы иметь возможность экспортировать это значение, удалив @JsonIgnore, добавив @JsonFormat и зарегистрировав JavaTimeModule в objectMapper:
record LogMessage(Level level, Long timestamp, String message) {
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd-MM-yyyy hh:mm:ss")
public ZonedDateTime getZonedDateTime() {
return ZonedDateTime.ofInstant(Instant.ofEpochMilli(timestamp()), ZoneId.of("UTC"));
}
}
objectMapper.registerModule(new JavaTimeModule());
System.out.println(objectMapper
.writerWithDefaultPrettyPrinter()
.writeValueAsString(logMessages));
Это генерирует JSON с ZonedDateTime в определенном формате:
[ {
"level" : 1,
"timestamp" : 1675867184342,
"message" : "Program started",
"zonedDateTime" : "08-02-2023 02:39:44"
}, {
"level" : 2,
"timestamp" : 1675867185921,
"message" : "File X not found",
"zonedDateTime" : "08-02-2023 02:39:45"
}, {
"level" : 3,
"timestamp" : 1675867186357,
"message" : "Error at line Y",
"zonedDateTime" : "08-02-2023 02:39:46"
} ]
Заключение
Перечисления Java могут содержать гораздо больше, чем просто список определений, но также данные и расширенную функциональность!
Пожалуйста, поэкспериментируйте с данным примером файла кода и узнайте, как вы можете использовать его в качестве основы для улучшения своего кода...
Первоначально опубликовано на https://webtechie.be.