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 MapBY_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 MapBY_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 .