Рубрики
Без рубрики

Путеводитель по Java Enums

Быстрое и практическое руководство по использованию Java Enum, что это такое, какие проблемы он решает и как он может быть использован для реализации часто используемых шаблонов проектирования.

Автор оригинала: baeldung.

1. Обзор

В этой статье мы увидим, что такое Java enums, какие проблемы они решают и как некоторые из шаблонов проектирования они могут быть использованы на практике.

enum ключевое слово было введено в Java 5. Он обозначает особый тип класса, который всегда расширяет java.lang.Enum класс. Для официальной документации об их использовании посмотрите на документация .

Константы, определенные таким образом, делают код более читаемым, позволяют проверять время компиляции, документировать заранее список принятых значений и избегать неожиданного поведения из-за недействительными значениями, передаваемыми.

Вот быстрый и простой пример enum, который определяет статус заказа на пиццу; статус заказа может быть ЗАКАЗАЛ , ГОТОВЫе или ДОСТАВЛЕННЫЕ :

public enum PizzaStatus {
    ORDERED,
    READY, 
    DELIVERED; 
}

Кроме того, они приходят со многими полезными методами, которые вы в противном случае придется написать себе, если вы используете традиционные общественные статические окончательные константы.

2. Пользовательские методы Enum

Итак, теперь, когда у нас есть базовое понимание того, что такое enums и как вы можете их использовать, давайте возьмем наш предыдущий пример на следующий уровень, определив некоторые дополнительные методы API на enum:

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 с использованием оператора

Так как типы enum гарантируют, что в СПМ существует только один экземпляр констант, мы можем безопасно использовать » оператор обеспечивает компил-время и безопасность времени пользования.

Давайте сначала посмотрим в день безопасности в в следующем фрагменте, где оператор “Я” используется для сравнения статусов и NullPointerЭксцепция не будет брошен, если значение будет нулевой . И наоборот, NullPointerЭксцепция будут брошены, если равный метод были использованы:

if(testPz.getStatus().equals(Pizza.PizzaStatus.DELIVERED)); 
if(testPz.getStatus() == Pizza.PizzaStatus.DELIVERED); 

Что касается составить правила безопасности , Давайте посмотрим на другой пример, где enum другого типа сравнивается с помощью равняется метод определяется как истинный – потому что значения enum и getStatus метод случайно один и тот же, но логически сравнение должно быть ложным. Этой проблемы можно избежать с помощью оператора “К”.

Компилятор будет помеять сравнение как ошибку несовместимости:

if(testPz.getStatus().equals(TestColor.GREEN));
if(testPz.getStatus() == TestColor.GREEN);

4. Использование типов Enum в заявлениях коммутатора

Типы enum могут быть использованы в переключить заявления также:

public int getDeliveryTimeInDays() {
    switch (status) {
        case ORDERED: return 5;
        case READY: return 2;
        case DELIVERED: return 0;
    }
    return 0;
}

5. Поля, методы и конструкторы в Enums

Можно определить конструкторы, методы и поля внутри типов enum, которые делают его очень мощным.

Давайте расширим приведенный выше пример и реализуем переход от одной стадии пиццы к другой и посмотрим, как мы сможем избавиться от если заявление и переключить заявление, используемое ранее:

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 и EnumMap

6.1. ЭнумСет

EnumSet является специализированной Установить реализация, предназначенная для использования с Энум Типы.

Это очень эффективное и компактное представление конкретного Установить Энум константы по сравнению с HashSet , в связи с внутренними Бит Вектор Представительство который используется. И это обеспечивает типобезопасную альтернативу традиционным int на основе “бит флаги”, что позволяет нам писать краткий код, который является более читаемым и поддерживать.

EnumSet это абстрактный класс, который имеет две реализации, называемые РегулярныеEnumSet и JumboEnumSet , одна из которых выбирается в зависимости от количества констант в enum во время мгновенного.

Поэтому это всегда хорошая идея, чтобы использовать этот набор всякий раз, когда мы хотим работать с коллекцией enum константы в большинстве сценариев (например, субсбор, добавление, удаление, и для объемных операций, как содержитВсе и removeAll ) и использовать Enum.values() если вы просто хотите, чтобы итерировать над всеми возможными константами.

В фрагменте кода ниже вы можете увидеть, как EnumSet используется для создания подмножества констант и их использования:

public class Pizza {

    private static EnumSet 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 getAllUndeliveredPizzas(List 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 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 undeliveredPzs = Pizza.getAllUndeliveredPizzas(pzList); 
    assertTrue(undeliveredPzs.size() == 3); 
}

6.2. EnumMap

EnumMap является специализированной Карта реализация предназначена для использования с enum константы в качестве ключей. Это эффективная и компактная реализация по сравнению с ее аналогом HashMap и внутренне представлен как массив:

EnumMap map;

Давайте кратко рассмотрим реальный пример, который показывает, как он может быть использован на практике:

public static EnumMap> 
  groupPizzaByStatus(List pizzaList) {
    EnumMap> pzByStatus = 
      new EnumMap>(PizzaStatus.class);
    
    for (Pizza pz : pizzaList) {
        PizzaStatus status = pz.getStatus();
        if (pzByStatus.containsKey(status)) {
            pzByStatus.get(status).add(pz);
        } else {
            List newPzList = new ArrayList();
            newPzList.add(pz);
            pzByStatus.put(status, newPzList);
        }
    }
    return pzByStatus;
}

Выполнение следующего теста продемонстрировало силу EnumMap реализации Карта интерфейс:

@Test
public void givenPizaOrders_whenGroupByStatusCalled_thenCorrectlyGrouped() {
    List 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> 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. Реализация шаблонов проектирования с использованием Enums

7.1. Синглтон шаблон

Как правило, реализация класса с использованием шаблона Singleton является довольно нетривиальным. Enums обеспечивают простой и быстрый способ реализации синглтонов.

В дополнение к этому, так как класс enum реализует Серийный интерфейс под капотом, класс гарантированно будет синглтон JVM, который в отличие от обычной реализации, где мы должны гарантировать, что никаких новых экземпляров не создаются во время deserialization.

В фрагменте кода ниже мы видим, как мы можем реализовать однотонный шаблон:

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;
    }
}

7.2. Шаблон стратегии

Условно шаблон Стратегии написан наличием интерфейса, который реализуется различными классами.

Добавление новой стратегии означало добавление нового класса реализации. С помощью enums это достигается с меньшими усилиями, добавление новой реализации означает определение просто еще одного экземпляра с некоторой реализацией.

Фрагмент кода ниже показывает, как реализовать шаблон Стратегии:

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);
}

Добавьте в следующий Пицца

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 и Enums

Пицца класс может быть переписан на Java 8, и вы можете увидеть, как методы getAllUndeliveredPizzas () и groupPizzaByStatus () стать настолько кратким с использованием lambdas и Поток пчела:

public static List getAllUndeliveredPizzas(List input) {
    return input.stream().filter(
      (s) -> !deliveredPizzaStatuses.contains(s.getStatus()))
        .collect(Collectors.toList());
}

public static EnumMap> 
  groupPizzaByStatus(List pzList) {
    EnumMap> map = pzList.stream().collect(
      Collectors.groupingBy(Pizza::getStatus,
      () -> new EnumMap<>(PizzaStatus.class), Collectors.toList()));
    return map;
}

9. Представительство Енума JSON

Используя библиотеки Джексона, можно иметь представление JSON типов enum, как если бы они являются POJOs. Фрагмент кода ниже показывает аннотации Джексона, которые могут быть использованы для того же:

@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));

для создания следующего представления JSON Пицца s статус:

{
  "status" : {
    "timeToDelivery" : 2,
    "ready" : true,
    "ordered" : false,
    "delivered" : false
  },
  "deliverable" : true
}

Для получения дополнительной информации о JSON сериализации/десериализации (в том числе настройки) enum типов относятся к Джексон – Сериализация Enums как JSON объектов .

10. Заключение

В этой статье мы исследовали Java enum, от основ языка до более продвинутых и интересных реальных случаев использования.

Фрагменты кода из этой статьи можно найти в Гитхуб хранилище.