Перейти к основному содержимому

Присоединение значений к Java Enum

· 6 мин. чтения

1. Обзор

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

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

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

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

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

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

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

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

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

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

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

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

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

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

public final String label;

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

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

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

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

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

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

System.out.println(BE.label);

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

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

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

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

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

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

Для этого мы можем добавить статический метод:

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

Статический метод valueOfLabel() перебирает значения Element , пока не найдет совпадение. Возвращает null , если совпадений не найдено. И наоборот, вместо возврата null может быть сгенерировано исключение .

Давайте посмотрим на быстрый пример использования нашего метода valueOfLabel() :

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

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

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

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

public enum Element {

// ... enum values

private static final Map<String, Element> 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);
}
}

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

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

6. Присоединение нескольких значений

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

Для иллюстрации давайте добавим атомный номер в виде int и атомный вес в виде числа с плавающей запятой :

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

private static final Map<String, Element> BY_LABEL = new HashMap<>();
private static final Map<Integer, Element> BY_ATOMIC_NUMBER = new HashMap<>();
private static final Map<Float, Element> 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);
}
}

Точно так же мы можем добавить в enum любые значения, которые захотим , например, соответствующие символы регистра, например, «He», «Li» и «Be».

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

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

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

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

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

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

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

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

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

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

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

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

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

public interface Labeled {
String label();
}

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

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

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

public enum Element implements Labeled {

// ...

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

// ...
}

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

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

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

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