Java Enums
Определяйте фиксированный набор констант в Java с помощью типов enum, включая enum с полями, конструкторами и методами.
enum — это класс, экземпляры которого представляют фиксированный, известный на этапе компиляции набор значений. Дни недели, цвета светофора, статусы заказов — всё, где допустимые значения образуют небольшой именованный список. Как только вы пишете enum Status { OPEN, CLOSED }, эти два значения становятся единственными возможными значениями Status, и компилятор сообщит, если вы попытаетесь создать третье.
Enums заменяют старую привычку из языка C писать public static final int OPEN = 0;. Целочисленные константы не дают никакой типобезопасности — setStatus(7) скомпилируется, — но setStatus(Status status) принимает только одно из объявленных значений.
Объявление enum
Простейшая форма — это просто список имён констант:
public enum Direction {
NORTH, EAST, SOUTH, WEST
}Используйте его как любой другой тип:
Direction d = Direction.NORTH;
if (d == Direction.NORTH) System.out.println("heading up");Каждая константа является единственным экземпляром Direction. Сравнение с помощью == правильно и идиоматично — в JVM существует ровно один NORTH, поэтому равенство ссылок совпадает с равенством значений.
В switch
Enums и switch созданы друг для друга:
switch (d) {
case NORTH -> System.out.println("up");
case SOUTH -> System.out.println("down");
case EAST, WEST -> System.out.println("sideways");
}Обратите внимание на просто NORTH вместо Direction.NORTH — внутри switch по enum компилятор знает тип. Если вы добавите новую константу и забудете обработать её в выражении switch, вы получите ошибку компиляции о неполноте, что именно и является нужной вам защитой.
Поля, конструкторы и методы
Enum может содержать данные и поведение. Каждая константа создаётся с аргументами, и тело enum может определять обычные методы:
public enum Planet {
MERCURY(3.303e+23, 2.4397e6),
EARTH (5.976e+24, 6.37814e6),
MARS (6.421e+23, 3.3972e6);
private final double massKg;
private final double radiusM;
Planet(double massKg, double radiusM) {
this.massKg = massKg;
this.radiusM = radiusM;
}
public double surfaceGravity() {
final double G = 6.67300E-11;
return G * massKg / (radiusM * radiusM);
}
}Два правила, которые нужно запомнить:
- Список констант идёт первым в теле, отделяется от остального содержимого точкой с запятой.
- Конструктор неявно является private — enums нельзя создавать снаружи, что и является их главным смыслом.
Встроенные методы
Каждый enum получает несколько методов бесплатно:
name()возвращает объявленное имя константы в видеString.ordinal()возвращает её порядковый номер (начиная с нуля) в объявлении. Иногда полезен; избегайте его сохранения, поскольку перестановка констант молча изменит смысл.values()возвращает массив всех констант в порядке объявления — удобен для цикловfor (Direction d : Direction.values()). При каждом вызове создаётся новый массив, поэтому в высоконагруженных циклах кэшируйте его в локальной переменной.valueOf(String)ищет константу по её точному имени (с учётом регистра) и выбрасываетIllegalArgumentException, если совпадение не найдено.
Поскольку valueOf выбрасывает исключение при неизвестном вводе, защищайтесь от него, когда строка приходит извне вашего кода — пользовательский ввод, файл, сетевые данные:
Direction d = Direction.valueOf("EAST"); // EAST
try {
Direction.valueOf("UP"); // not a declared constant
} catch (IllegalArgumentException e) {
System.out.println("no such constant"); // no such constant
}Поведение на уровне констант
Иногда одна константа должна вести себя иначе, чем остальные. Вы можете переопределить методы для каждой константы с помощью анонимного тела:
public enum Operation {
PLUS { public int apply(int a, int b) { return a + b; } },
MINUS { public int apply(int a, int b) { return a - b; } },
TIMES { public int apply(int a, int b) { return a * b; } };
public abstract int apply(int a, int b);
}Каждая константа становится маленьким анонимным подклассом Operation, реализующим apply. Это бесплатно даёт вам паттерн «стратегия».
Реализация интерфейсов
Enums могут реализовывать интерфейсы — они просто не могут расширять другие классы (они неявно расширяют java.lang.Enum):
public enum Day implements Runnable {
MONDAY, TUESDAY;
public void run() { System.out.println("today is " + name()); }
}Это чистый способ подключить фиксированное семейство стратегий в код, который ожидает любой Runnable.
EnumSet и EnumMap
Когда нужен Set или Map с ключом в виде enum, предпочтите EnumSet и EnumMap вместо HashSet/HashMap. Оба реализованы через битовый вектор или простой массив, размер которого соответствует enum, поэтому хеширование полностью исключено — поиск выполняется за один индекс массива, а EnumSet небольшого enum помещается в один long:
EnumSet<Direction> horizontal = EnumSet.of(Direction.EAST, Direction.WEST);
EnumSet<Direction> all = EnumSet.allOf(Direction.class); // every constant
EnumSet<Direction> none = EnumSet.noneOf(Direction.class); // empty, ready to fill
EnumMap<Direction, String> labels = new EnumMap<>(Direction.class);
labels.put(Direction.NORTH, "up");Итерация по EnumSet или EnumMap следует порядку объявления, что делает их вывод детерминированным — ещё одна причина предпочитать их хеш-коллекциям.
Практический пример
Что дальше
Enums фиксируют набор экземпляров. В следующей главе рассматривается другая форма ограниченного класса — records, предназначенные для простых неизменяемых носителей данных. Перейдите к Java records.