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 .