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

Прикрепление значений к перечислению Java

Изучите особенности реализации перечисления Java.

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

1. Обзор

Тип перечисления Java обеспечивает поддерживаемый языком способ создания и использования постоянных значений. Определяя конечный набор значений, enum является более типобезопасным, чем постоянные литеральные переменные, такие как String или int .

Однако значения enum должны быть допустимыми идентификаторами , и нам рекомендуется использовать SCREAMING_SNAKE_CASE по соглашению.

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

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

Дальнейшее чтение:

Руководство по перечислениям Java

Перебор значений перечисления в Java

Руководство по конструкторам в Java

2. Использование перечисления Java в качестве класса

Мы часто создаем перечисление в виде простого списка значений. Например, вот первые две строки периодической таблицы в виде простого перечисления :

public enum Element {
    H, HE, LI, BE, B, C, N, O, F, NE
}

Используя приведенный выше синтаксис, мы создали десять статических конечных экземпляров перечисления с именем Элемента . Хотя это очень эффективно, мы захватили только символы элементов. И хотя форма верхнего регистра подходит для констант Java, это не то, как мы обычно пишем символы.

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

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

3. Добавление конструктора и Конечного поля

Давайте начнем с добавления имен элементов.

Мы установим имена в переменную final с помощью конструктора :

public enum Element {
    H("Hydrogen"),
    HE("Helium"),
    // ...
    NE("Neon");

    public final String label;

    private Element(String label) {
        this.label = label;
    }
}

Прежде всего, мы обращаем внимание на специальный синтаксис в списке объявлений. Именно так вызывается конструктор для типов enum . Хотя использование оператора new для перечисления незаконно , мы можем передавать аргументы конструктора в списке объявлений.

Затем мы объявляем переменную экземпляра label . Есть несколько вещей, которые следует отметить по этому поводу.

Во-первых, мы выбрали этикетка идентификатор вместо имя . Хотя поле член имя доступен для использования, давайте выберем этикетка чтобы избежать путаницы с предопределенными Enum.name() метод.

Во-вторых, наше поле метки является окончательным . Хотя поля перечисления не обязательно должны быть окончательными , в большинстве случаев мы не хотим, чтобы наши метки менялись. В духе перечисления значения постоянны, это имеет смысл.

Наконец, поле label является общедоступным, поэтому мы можем получить прямой доступ к метке:

System.out.println(BE.label);

С другой стороны, поле может быть закрытым , доступ к которому осуществляется с помощью метода getLabel () . В целях краткости в этой статье будет по-прежнему использоваться стиль публичного поля.

4. Поиск значений перечисления Java

Java предоставляет метод valueOf(String) для всех типов enum .

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

assertSame(Element.LI, Element.valueOf("LI"));

Однако мы также можем захотеть найти значение enum по нашему полю метки.

Для этого мы можем добавить метод static :

public static Element valueOfLabel(String label) {
    for (Element e : values()) {
        if (e.label.equals(label)) {
            return e;
        }
    }
    return null;
}

Статическое значение метода Label() повторяет значения элемента до тех пор, пока не найдет совпадение. Он возвращает null , если совпадение не найдено. И наоборот, вместо возврата null может быть выдано исключение .

Давайте рассмотрим краткий пример, используя наш метод value Of Label() :

assertSame(Element.LI, Element.valueOfLabel("Lithium"));

5. Кэширование значений поиска

Мы можем избежать повторения перечисление значения с помощью Карта чтобы кэшировать метки.

Для этого мы определяем статическую конечную карту и заполняем ее при загрузке класса:

public enum Element {

    // ... enum values

    private static final Map BY_LABEL = new HashMap<>();
    
    static {
        for (Element e: values()) {
            BY_LABEL.put(e.label, e);
        }
    }

   // ... fields, constructor, methods

    public static Element valueOfLabel(String label) {
        return BY_LABEL.get(label);
    }
}

В результате кэширования значения enum повторяются только один раз , а значение |/метода Label() упрощается.

В качестве альтернативы мы можем лениво построить кэш, когда к нему впервые обращаются в методе value Of Label () . В этом случае доступ к карте должен быть синхронизирован, чтобы предотвратить проблемы параллелизма.

6. Прикрепление Нескольких Значений

То Перечисление конструктор может принимать несколько значений.

Чтобы проиллюстрировать это, давайте добавим атомный номер в виде int и атомный вес в виде float :

public enum Element {
    H("Hydrogen", 1, 1.008f),
    HE("Helium", 2, 4.0026f),
    // ...
    NE("Neon", 10, 20.180f);

    private static final Map BY_LABEL = new HashMap<>();
    private static final Map BY_ATOMIC_NUMBER = new HashMap<>();
    private static final Map BY_ATOMIC_WEIGHT = new HashMap<>();
    
    static {
        for (Element e : values()) {
            BY_LABEL.put(e.label, e);
            BY_ATOMIC_NUMBER.put(e.atomicNumber, e);
            BY_ATOMIC_WEIGHT.put(e.atomicWeight, e);
        }
    }

    public final String label;
    public final int atomicNumber;
    public final float atomicWeight;

    private Element(String label, int atomicNumber, float atomicWeight) {
        this.label = label;
        this.atomicNumber = atomicNumber;
        this.atomicWeight = atomicWeight;
    }

    public static Element valueOfLabel(String label) {
        return BY_LABEL.get(label);
    }

    public static Element valueOfAtomicNumber(int number) {
        return BY_ATOMIC_NUMBER.get(number);
    }

    public static Element valueOfAtomicWeight(float weight) {
        return BY_ATOMIC_WEIGHT.get(weight);
    }
}

Аналогично, мы можем добавить в перечисление любые значения , которые мы хотим, например, символы правильного регистра “He”, “Li” и “Be”.

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

7. Управление интерфейсом

В результате добавления полей и методов в наш enum мы изменили его открытый интерфейс. Поэтому наш код, который использует методы core Enum | name () и valueOf () , не будет знать о наших новых полях.

То статический Значение() метод уже определен для нас языком Java, поэтому мы не можем предоставить свой собственный Значение() реализация.

Аналогично, потому что Enum.name() метод окончательный , мы также не можем его переопределить.

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

7.1. Переопределение toString()

Переопределение toString() может быть альтернативой переопределению name() :

@Override 
public String toString() { 
    return this.label; 
}

По умолчанию Enum.toString() возвращает то же значение, что и Enum.name().

7.2. Реализация интерфейса

Тип enum в Java может реализовывать интерфейсы. Хотя этот подход не является таким общим, как Перечисление API, интерфейсы действительно помогают нам обобщать.

Давайте рассмотрим этот интерфейс:

public interface Labeled {
    String label();
}

Для согласованности с Enum.name() метод, наш метод label() не имеет префикса get .

И поскольку значение метода Label() является статическим , мы не включаем его в наш интерфейс.

Наконец, мы можем реализовать интерфейс в нашем перечислении :

public enum Element implements Labeled {

    // ...

    @Override
    public String label() {
        return label;
    }

    // ...
}

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

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

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

Как всегда, полный исходный код этой статьи можно найти на GitHub .