W3docs

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 следует порядку объявления, что делает их вывод детерминированным — ещё одна причина предпочитать их хеш-коллекциям.

Практический пример

java— editable, runs on the server

Что дальше

Enums фиксируют набор экземпляров. В следующей главе рассматривается другая форма ограниченного класса — records, предназначенные для простых неизменяемых носителей данных. Перейдите к Java records.

Практика

Практика
Какова главная причина использовать enum вместо набора int-констант?
Какова главная причина использовать enum вместо набора int-констант?
Was this page helpful?