Недавно я пересмотрел перечисление Java и увидел эту статью, которая мне показалась неплохой, поэтому я просто перевел и улучшил часть контента и поделился им со всеми, надеюсь, вы тоже сможете что-то получить. Кроме того, не забудьте добавить в конце статьи!
ps: Вот статья о перечислении, в том числе потому, что я опубликую очень практическую передовую практику глобальной обработки исключений SpringBoot, в которой используется перечисление.
Эта статья была переведена JavaGuide, общедоступная учетная запись: JavaGuide, исходный адрес: https://www.baeldung.com/a-guide-to-java-enums.
При перепечатке просьба указывать вышеуказанный текст.
1. Обзор
В этой статье мы увидим, что такое перечисления Java, какие проблемы они решают и как на практике реализовать некоторые шаблоны проектирования с использованием перечислений Java.
Ключевое слово enum было введено в java5, указывая на особый тип класса, который всегда наследует класс java.lang.Enum.Для получения дополнительной информации вы можете просмотреть его собственныйофициальная документация.
Перечисления часто сравнивают с константами, вероятно, потому, что мы используем много перечислений для замены констант. Итак, каковы преимущества этого подхода?
Константы, определенные таким образом, делают код более читабельным, позволяют выполнять проверки во время компиляции, предварительно записывают список допустимых значений и позволяют избежать неожиданного поведения, вызванного передачей недопустимых значений.
В следующем примере определяется статус заказа пиццы в виде простого перечисления, есть три состояния ЗАКАЗАН, ГОТОВ, ДОСТАВЛЕН:
package shuang.kou.enumdemo.enumtest;
public enum PizzaStatus {
ORDERED,
READY,
DELIVERED;
}
Короче говоря, мы избегаем определения констант в приведенном выше коде и помещаем все константы, связанные со статусом заказа пиццы, в тип перечисления.
System.out.println(PizzaStatus.ORDERED.name());//ORDERED
System.out.println(PizzaStatus.ORDERED);//ORDERED
System.out.println(PizzaStatus.ORDERED.name().getClass());//class java.lang.String
System.out.println(PizzaStatus.ORDERED.getClass());//class shuang.kou.enumdemo.enumtest.PizzaStatus
2. Пользовательский метод перечисления
Теперь, когда у нас есть общее представление о том, что такое перечисления и как их использовать, давайте поднимем предыдущий пример на новый уровень, определив некоторые дополнительные методы API для перечислений:
public class Pizza {
private PizzaStatus status;
public enum PizzaStatus {
ORDERED,
READY,
DELIVERED;
}
public boolean isDeliverable() {
if (getStatus() == PizzaStatus.READY) {
return true;
}
return false;
}
// Methods that set and get the status variable.
}
3. Используйте == для сравнения типов перечисления
Поскольку тип enum гарантирует, что в JVM существует только один экземпляр константы, мы можем безопасно использовать оператор «==» для сравнения двух переменных, как в приведенном выше примере; более того, оператор «==» время и безопасность во время выполнения.
Во-первых, давайте взглянем на безопасность во время выполнения в следующем фрагменте кода, где оператор «==» используется для сравнения состояний, и ни один из них не создает исключение NullPointerException, если оба значения равны нулю. И наоборот, если используется метод equals, будет выброшено исключение NullPointerException:
if(testPz.getStatus().equals(Pizza.PizzaStatus.DELIVERED));
if(testPz.getStatus() == Pizza.PizzaStatus.DELIVERED);
Для безопасности во время компиляции давайте рассмотрим другой пример, сравниваются два разных типа перечисления, и результат сравнения с использованием метода equal определяется как истинный, поскольку значение перечисления метода getStatus согласуется со значением перечисления метода другой тип, но по логике он должен быть ложным. Этой проблемы можно избежать, используя оператор ==. Потому что компилятор скажет ошибку несовместимости типов:
if(testPz.getStatus().equals(TestColor.GREEN));
if(testPz.getStatus() == TestColor.GREEN);
4. Используйте типы enum в операторах switch
public int getDeliveryTimeInDays() {
switch (status) {
case ORDERED: return 5;
case READY: return 2;
case DELIVERED: return 0;
}
return 0;
}
5. Свойства, методы и конструкторы перечисляемых типов
В конце статьи есть мои (JavaGuide) дополнения.
Вы можете сделать его еще более мощным, определив свойства, методы и конструкторы для типов перечислений.
Далее давайте расширим приведенный выше пример, чтобы реализовать переход от одного этапа пиццы к другому, и посмотрим, как избавиться от операторов if и switch, которые мы использовали ранее:
public class Pizza {
private PizzaStatus status;
public enum PizzaStatus {
ORDERED (5){
@Override
public boolean isOrdered() {
return true;
}
},
READY (2){
@Override
public boolean isReady() {
return true;
}
},
DELIVERED (0){
@Override
public boolean isDelivered() {
return true;
}
};
private int timeToDelivery;
public boolean isOrdered() {return false;}
public boolean isReady() {return false;}
public boolean isDelivered(){return false;}
public int getTimeToDelivery() {
return timeToDelivery;
}
PizzaStatus (int timeToDelivery) {
this.timeToDelivery = timeToDelivery;
}
}
public boolean isDeliverable() {
return this.status.isReady();
}
public void printTimeToDeliver() {
System.out.println("Time to delivery is " +
this.getStatus().getTimeToDelivery());
}
// Methods that set and get the status variable.
}
Следующий код показывает, как это работает:
@Test
public void givenPizaOrder_whenReady_thenDeliverable() {
Pizza testPz = new Pizza();
testPz.setStatus(Pizza.PizzaStatus.READY);
assertTrue(testPz.isDeliverable());
}
6.EnumSet and EnumMap
6.1. EnumSet
EnumSet
это специальный дизайн для типов перечисленияSet
тип.
иHashSet
Напротив, он специфичен из-за использования внутреннего представления битового вектора.Enum
Очень эффективное и компактное представление постоянного множества.
Он предоставляет типобезопасную альтернативу традиционным «битовым флагам» на основе int, позволяя нам писать краткий код, более читабельный и простой в обслуживании.
EnumSet
это абстрактный класс с двумя реализациями:RegularEnumSet
,JumboEnumSet
, какой из них выбран, зависит от количества констант в перечислении во время создания экземпляра.
Операции с набором констант перечисления во многих сценариях (например, подмножество, добавление, удаление,containsAll
иremoveAll
пакетная операция) использоватьEnumSet
Очень подходит; используйте, если вам нужно перебрать все возможные константыEnum.values()
.
public class Pizza {
private static EnumSet<PizzaStatus> undeliveredPizzaStatuses =
EnumSet.of(PizzaStatus.ORDERED, PizzaStatus.READY);
private PizzaStatus status;
public enum PizzaStatus {
...
}
public boolean isDeliverable() {
return this.status.isReady();
}
public void printTimeToDeliver() {
System.out.println("Time to delivery is " +
this.getStatus().getTimeToDelivery() + " days");
}
public static List<Pizza> getAllUndeliveredPizzas(List<Pizza> input) {
return input.stream().filter(
(s) -> undeliveredPizzaStatuses.contains(s.getStatus()))
.collect(Collectors.toList());
}
public void deliver() {
if (isDeliverable()) {
PizzaDeliverySystemConfiguration.getInstance().getDeliveryStrategy()
.deliver(this);
this.setStatus(PizzaStatus.DELIVERED);
}
}
// Methods that set and get the status variable.
}
Следующий тест демонстрируетEnumSet
Мощные функции в определенных сценариях:
@Test
public void givenPizaOrders_whenRetrievingUnDeliveredPzs_thenCorrectlyRetrieved() {
List<Pizza> pzList = new ArrayList<>();
Pizza pz1 = new Pizza();
pz1.setStatus(Pizza.PizzaStatus.DELIVERED);
Pizza pz2 = new Pizza();
pz2.setStatus(Pizza.PizzaStatus.ORDERED);
Pizza pz3 = new Pizza();
pz3.setStatus(Pizza.PizzaStatus.ORDERED);
Pizza pz4 = new Pizza();
pz4.setStatus(Pizza.PizzaStatus.READY);
pzList.add(pz1);
pzList.add(pz2);
pzList.add(pz3);
pzList.add(pz4);
List<Pizza> undeliveredPzs = Pizza.getAllUndeliveredPizzas(pzList);
assertTrue(undeliveredPzs.size() == 3);
}
6.2. EnumMap
EnumMap
— это специализированная реализация карты для использования констант перечисления в качестве ключей. с соответствующимHashMap
Напротив, это эффективная и компактная реализация, внутренне представленная в виде массива:
EnumMap<Pizza.PizzaStatus, Pizza> map;
Давайте кратко рассмотрим реальный пример, который демонстрирует, как использовать его на практике:
public static EnumMap<PizzaStatus, List<Pizza>>
groupPizzaByStatus(List<Pizza> pizzaList) {
EnumMap<PizzaStatus, List<Pizza>> pzByStatus =
new EnumMap<PizzaStatus, List<Pizza>>(PizzaStatus.class);
for (Pizza pz : pizzaList) {
PizzaStatus status = pz.getStatus();
if (pzByStatus.containsKey(status)) {
pzByStatus.get(status).add(pz);
} else {
List<Pizza> newPzList = new ArrayList<Pizza>();
newPzList.add(pz);
pzByStatus.put(status, newPzList);
}
}
return pzByStatus;
}
Следующий тест демонстрируетEnumMap
Мощные функции в определенных сценариях:
@Test
public void givenPizaOrders_whenGroupByStatusCalled_thenCorrectlyGrouped() {
List<Pizza> pzList = new ArrayList<>();
Pizza pz1 = new Pizza();
pz1.setStatus(Pizza.PizzaStatus.DELIVERED);
Pizza pz2 = new Pizza();
pz2.setStatus(Pizza.PizzaStatus.ORDERED);
Pizza pz3 = new Pizza();
pz3.setStatus(Pizza.PizzaStatus.ORDERED);
Pizza pz4 = new Pizza();
pz4.setStatus(Pizza.PizzaStatus.READY);
pzList.add(pz1);
pzList.add(pz2);
pzList.add(pz3);
pzList.add(pz4);
EnumMap<Pizza.PizzaStatus,List<Pizza>> map = Pizza.groupPizzaByStatus(pzList);
assertTrue(map.get(Pizza.PizzaStatus.DELIVERED).size() == 1);
assertTrue(map.get(Pizza.PizzaStatus.ORDERED).size() == 2);
assertTrue(map.get(Pizza.PizzaStatus.READY).size() == 1);
}
7. Реализуйте некоторые шаблоны проектирования с помощью перечисления
7.1 Одноэлементный шаблон
В общем, реализация шаблона Singleton с помощью классов не является тривиальной, а перечисления обеспечивают простой способ реализации синглетонов.
"Эффективная Java" и "Java и шаблоны" настоятельно рекомендуют этот подход. Каковы преимущества реализации перечисления таким образом?
«Эффективная Java»
Этот метод аналогичен по функциям общедоступному методу, но он более лаконичен, предоставляет бесплатный механизм сериализации и полностью предотвращает создание нескольких экземпляров даже перед лицом сложных атак сериализации или отражения. Хотя этот подход не получил широкого распространения, одноэлементные перечисляемые типы стали лучшим способом реализации Singleton. —— «Эффективное издание Java на китайском языке, второе издание»
Java и шаблоны
В статье «Java и шаблоны» автор написал, что более лаконично использовать перечисление для реализации управления одним экземпляром и предоставляет бесплатный механизм сериализации, а JVM принципиально гарантирует абсолютное предотвращение множественных экземпляров. эффективный и безопасный способ реализации синглетонов.
В следующем фрагменте кода показано, как реализовать одноэлементный шаблон с помощью перечислений:
public enum PizzaDeliverySystemConfiguration {
INSTANCE;
PizzaDeliverySystemConfiguration() {
// Initialization configuration which involves
// overriding defaults like delivery strategy
}
private PizzaDeliveryStrategy deliveryStrategy = PizzaDeliveryStrategy.NORMAL;
public static PizzaDeliverySystemConfiguration getInstance() {
return INSTANCE;
}
public PizzaDeliveryStrategy getDeliveryStrategy() {
return deliveryStrategy;
}
}
Как это использовать? См. код ниже:
PizzaDeliveryStrategy deliveryStrategy = PizzaDeliverySystemConfiguration.getInstance().getDeliveryStrategy();
пройти черезPizzaDeliverySystemConfiguration.getInstance()
Полученный синглтонPizzaDeliverySystemConfiguration
7.2 Режим стратегии
Обычно шаблон стратегии реализуется разными классами, реализующими один и тот же интерфейс.
Это также означает, что добавление новых стратегий означает добавление новых классов реализации. С перечислениями это можно сделать легко, добавление новой реализации означает просто определение другого экземпляра с реализацией.
В следующем фрагменте кода показано, как реализовать шаблон стратегии с помощью перечислений:
public enum PizzaDeliveryStrategy {
EXPRESS {
@Override
public void deliver(Pizza pz) {
System.out.println("Pizza will be delivered in express mode");
}
},
NORMAL {
@Override
public void deliver(Pizza pz) {
System.out.println("Pizza will be delivered in normal mode");
}
};
public abstract void deliver(Pizza pz);
}
даватьPizza
Добавьте следующий метод:
public void deliver() {
if (isDeliverable()) {
PizzaDeliverySystemConfiguration.getInstance().getDeliveryStrategy()
.deliver(this);
this.setStatus(PizzaStatus.DELIVERED);
}
}
Как это использовать? См. код ниже:
@Test
public void givenPizaOrder_whenDelivered_thenPizzaGetsDeliveredAndStatusChanges() {
Pizza pz = new Pizza();
pz.setStatus(Pizza.PizzaStatus.READY);
pz.deliver();
assertTrue(pz.getStatus() == Pizza.PizzaStatus.DELIVERED);
}
8. Java 8 и перечисления
Класс пиццы можно переписать на Java 8, вы можете увидеть, как метод lambda и Stream API делаютgetAllUndeliveredPizzas()
иgroupPizzaByStatus()
Метод становится таким лаконичным:
getAllUndeliveredPizzas()
:
public static List<Pizza> getAllUndeliveredPizzas(List<Pizza> input) {
return input.stream().filter(
(s) -> !deliveredPizzaStatuses.contains(s.getStatus()))
.collect(Collectors.toList());
}
groupPizzaByStatus()
:
public static EnumMap<PizzaStatus, List<Pizza>>
groupPizzaByStatus(List<Pizza> pzList) {
EnumMap<PizzaStatus, List<Pizza>> map = pzList.stream().collect(
Collectors.groupingBy(Pizza::getStatus,
() -> new EnumMap<>(PizzaStatus.class), Collectors.toList()));
return map;
}
9. JSON-представление типа Enum
Используя библиотеку Джексона, можно представить JSON типов перечисления как POJO. В следующем фрагменте кода показаны аннотации Джексона, которые можно использовать для той же цели:
@JsonFormat(shape = JsonFormat.Shape.OBJECT)
public enum PizzaStatus {
ORDERED (5){
@Override
public boolean isOrdered() {
return true;
}
},
READY (2){
@Override
public boolean isReady() {
return true;
}
},
DELIVERED (0){
@Override
public boolean isDelivered() {
return true;
}
};
private int timeToDelivery;
public boolean isOrdered() {return false;}
public boolean isReady() {return false;}
public boolean isDelivered(){return false;}
@JsonProperty("timeToDelivery")
public int getTimeToDelivery() {
return timeToDelivery;
}
private PizzaStatus (int timeToDelivery) {
this.timeToDelivery = timeToDelivery;
}
}
Мы можем использовать следующим образомPizza
иPizzaStatus
:
Pizza pz = new Pizza();
pz.setStatus(Pizza.PizzaStatus.READY);
System.out.println(Pizza.getJsonString(pz));
Сгенерированное состояние Pizza показано в следующем JSON:
{
"status" : {
"timeToDelivery" : 2,
"ready" : true,
"ordered" : false,
"delivered" : false
},
"deliverable" : true
}
Дополнительные сведения о сериализации/десериализации JSON (включая настройку) типов перечислений см.Джексон — сериализовать перечисления в объекты JSON.
10. Резюме
В этой статье мы обсудили тип перечисления Java, от базовых знаний до продвинутых приложений и практических сценариев приложений, чтобы мы могли почувствовать силу перечисления.
11. Дополнение
Мы упоминали выше, что мы можем сделать его более мощным, определив свойства, методы и конструкторы для типов перечислений.
Позвольте мне показать на реальном примере, когда мы вызываем код подтверждения SMS, может быть несколько разных применений, мы определяем его следующим образом:
public enum PinType {
REGISTER(100000, "注册使用"),
FORGET_PASSWORD(100001, "忘记密码使用"),
UPDATE_PHONE_NUMBER(100002, "更新手机号码使用");
private final int code;
private final String message;
PinType(int code, String message) {
this.code = code;
this.message = message;
}
public int getCode() {
return code;
}
public String getMessage() {
return message;
}
@Override
public String toString() {
return "PinType{" +
"code=" + code +
", message='" + message + '\'' +
'}';
}
}
фактическое использование:
System.out.println(PinType.FORGET_PASSWORD.getCode());
System.out.println(PinType.FORGET_PASSWORD.getMessage());
System.out.println(PinType.FORGET_PASSWORD.toString());
Output:
100001
忘记密码使用
PinType{code=100001, message='忘记密码使用'}
Таким образом, он будет очень гибким и удобным в использовании на практике!
Рекомендация проекта с открытым исходным кодом
Другие рекомендации автора по проектам с открытым исходным кодом:
- JavaGuide: [Изучение Java + руководство для интервью] Обложка, содержащая основные знания, которые необходимо освоить большинству Java-программистов.
- springboot-guide: Учебное пособие по Spring Boot, подходящее для начинающих и опытных разработчиков (поддержка в свободное время, добро пожаловать в совместную поддержку).
- programmer-advancement: Я думаю, некоторые хорошие привычки, которые должны быть у техников!
- spring-security-jwt-guide:Начинать с нуля! Spring Security с JWT (включая проверку авторизации) бэкэнд-часть кода.