Интерфейсы, enum ·
‹ Предыдущий Следующий ›
⏱ 5 минут чтения Обновлено: 2026-09-29

Перечисления (enum) в Java: полное руководство с примерами

Что такое enum в Java

Представьте, что размер кофе хранится в строке: String size = "BIGG";. Опечатка спокойно компилируется, и ошибка всплывёт только тогда, когда клиент не получит свой кофе. Если объявить размер как enum, та же опечатка CoffeeSize.BIGG не пройдёт компиляцию.

Перечисление (enum) в Java — это специальный ссылочный тип (разновидность класса), у которого есть фиксированный заранее известный набор экземпляров — именованных констант. Английское слово enum — сокращение от enumeration, что переводится как «перечисление». Перечисления появились в Java 5 и объявляются с помощью ключевого слова enum.

Перечисления указывают возможные значения для какого-то явления. Например, вы открыли кофейню, в которой продаются три варианта кофе — BIG, HUGE и OVERWHELMING. Других вариантов быть не может. Если задавать значения с помощью String, можно передать любое другое значение, например MIDDLE или SMALL. Задавая перечисление, вы ограничиваете возможные варианты:

public enum CoffeeSize {
    BIG, HUGE, OVERWHELMING
}

В простейшей форме перечисление — это список именованных констант. Каждая константа (BIG, HUGE и OVERWHELMING) является объектом того перечисления, в котором она определена. Константы неявно объявлены как public static final: их нельзя переприсвоить, и каждая существует в единственном экземпляре.

Какой тип данных у перечисления

Каждое перечисление — это отдельный ссылочный тип данных. Объявив enum CoffeeSize, вы создаёте новый тип CoffeeSize, и переменная этого типа хранит ссылку на одну из его констант. Объекты создаются JVM один раз при загрузке класса перечисления, поэтому оператор new для enum не используется (и не компилируется). В отличие от примитивных типов, переменная перечисления может быть равна null:

public class CoffeeSizeExample1 {
    public static void main(String[] args) {
        CoffeeSize coffeeSize = CoffeeSize.BIG;
        if (coffeeSize == CoffeeSize.BIG) {
            System.out.println(coffeeSize); // BIG
        }
        CoffeeSize notChosen = null; // допустимо: enum - ссылочный тип
    }
}

Enum против String-констант

До Java 5 перечисления имитировали классом с константами:

public class CoffeeSizeConstants {
    public static final String BIG = "BIG";
    public static final String HUGE = "HUGE";
    public static final String OVERWHELMING = "OVERWHELMING";
}

Такой подход работает, но у enum гораздо больше преимуществ:

Критерий Константы String/int enum
Проверка типов Нет: метод примет любую строку Да: можно передать только константу этого enum
Перебор всех значений Вручную Метод values()
Поля, конструкторы, методы Нет Да
Использование в switch Да, но без контроля полноты Да, компилятор проверяет полноту в switch-выражении
Специализированные коллекции Нет EnumSet, EnumMap

Enum в операторе switch

Значения перечисления можно использовать в операторе switch. В ветвях case должны использоваться константы того же перечисления, что и в выражении switch. Имена констант указываются без имени типа: тип перечисления в switch уже неявно задаёт тип для меток case.

public class CoffeeSizeExample2 {
    public static void main(String[] args) {
        CoffeeSize coffeeSize = CoffeeSize.BIG;
        switch (coffeeSize) {
            case BIG:
                System.out.println("Дайте мне большую чашку кофе!");
                break;
            case HUGE:
                System.out.println("Дайте мне огромную чашку кофе!");
                break;
            case OVERWHELMING:
                System.out.println("Дайте мне громадную чашку кофе!");
                break;
            default:
                System.out.println("Чашка не выбрана.");
        }
    }
}

С Java 14 удобнее использовать switch-выражение со стрелками. Если перечислены все константы, ветка default не нужна: компилятор сам проверит полноту и выдаст ошибку, если в enum добавят новую константу, а switch забудут обновить.

String message = switch (coffeeSize) {
    case BIG -> "Большая чашка";
    case HUGE -> "Огромная чашка";
    case OVERWHELMING -> "Громадная чашка";
};

Нюанс

Если переменная enum равна null, классический switch выбросит NullPointerException — ветка default не спасёт. До Java 21 в case разрешено только простое имя константы (case BIG), запись case CoffeeSize.BIG не компилировалась; начиная с Java 21 квалифицированное имя тоже допустимо.

Класс java.lang.Enum и методы enum

Все перечисления неявно наследуют класс java.lang.Enum: компилятор сам добавляет extends Enum<CoffeeSize>. Поэтому явно написать extends у перечисления нельзя — в Java нет множественного наследования классов. Унаследовать перечисление обычным классом тоже нельзя: enum неявно final. Единственное исключение — константы с собственным телом (см. раздел о переопределении): для них компилятор создаёт анонимные подклассы, а сам enum становится неявно sealed (Java 17+).

Класс Enum реализует интерфейсы Comparable и Serializable, поэтому любое перечисление можно сравнивать и сериализовать. В Enum определены методы name(), ordinal(), compareTo(), equals(), toString() и другие. Статические методы values() и valueOf(String) в классе Enum не объявлены — компилятор добавляет их в каждое перечисление автоматически.

Метод Что возвращает Пример для CoffeeSize.HUGE
values() Массив всех констант в порядке объявления [BIG, HUGE, OVERWHELMING]
valueOf(String) Константу по точному имени valueOf("HUGE") → HUGE
name() Имя константы, как в коде "HUGE"
toString() По умолчанию то же, что name(), можно переопределить "HUGE"
ordinal() Порядковый номер, начиная с 0 1
compareTo(e) Разницу порядковых номеров HUGE.compareTo(BIG) → 1
equals(o) true, только если это та же константа HUGE.equals(CoffeeSize.HUGE) → true

Метод values()

Метод values() возвращает массив, содержащий все константы перечисления в порядке их объявления:

public class CoffeeSizeValuesExample {
    public static void main(String[] args) {
        System.out.println("Константы перечислимого типа CoffeeSize:");
        CoffeeSize[] coffeeSizes = CoffeeSize.values();
        for (CoffeeSize c : coffeeSizes) {
            System.out.println(c);
        }
    }
}

Результат выполнения:

Константы перечислимого типа CoffeeSize:
BIG
HUGE
OVERWHELMING

Каждый вызов values() создаёт новую копию массива, поэтому изменение возвращённого массива не повлияет на перечисление. В горячем цикле результат лучше сохранить в переменную.

Метод valueOf()

Статический метод valueOf() возвращает константу, имя которой совпадает со строкой-аргументом. Иначе говоря, он преобразует String в enum:

public class CoffeeSizeExample3 {
    public static void main(String[] args) {
        CoffeeSize coffeeSize = CoffeeSize.valueOf("BIG");
        System.out.println("Переменная coffeeSize содержит " + coffeeSize);

        CoffeeSize wrong = CoffeeSize.valueOf("big"); // IllegalArgumentException
    }
}

Имя сравнивается с учётом регистра и без обрезки пробелов. Если такой константы нет, выбрасывается IllegalArgumentException, а при передаче null — NullPointerException.

Методы name() и toString()

Метод name() объявлен как final и всегда возвращает имя константы точно так, как оно записано в коде. Метод toString() по умолчанию возвращает то же самое, но его можно переопределить, например, чтобы выводить название для пользователя. Для надёжного преобразования enum в строку и обратно (через valueOf()) используйте именно name().

Метод ordinal()

Метод ordinal() возвращает позицию константы в объявлении перечисления. Нумерация начинается с нуля:

public class CoffeeSizeExample4 {
    public static void main(String[] args) {
        for (CoffeeSize c : CoffeeSize.values()) {
            System.out.println(c + " " + c.ordinal());
        }
    }
}
BIG 0
HUGE 1
OVERWHELMING 2

Метод compareTo()

С помощью метода int compareTo(E e) можно сравнить порядковые значения двух констант одного и того же перечисления. Сравнить константы разных перечислений не получится — это ошибка компиляции.

  • Если порядковое значение вызывающей константы меньше, чем у e, возвращается отрицательное число.
  • Если это одна и та же константа, возвращается ноль.
  • Если порядковое значение вызывающей константы больше, возвращается положительное число.
public class CoffeeSizeCompareTo {
    public static void main(String[] args) {
        CoffeeSize bigCup = CoffeeSize.BIG;
        CoffeeSize hugeCup = CoffeeSize.HUGE;
        CoffeeSize anotherBigCup = CoffeeSize.BIG;
        CoffeeSize overwhelmingCup = CoffeeSize.OVERWHELMING;

        System.out.println("bigCup.compareTo(hugeCup): " + bigCup.compareTo(hugeCup));
        System.out.println("hugeCup.compareTo(bigCup): " + hugeCup.compareTo(bigCup));
        System.out.println("bigCup.compareTo(anotherBigCup): " + bigCup.compareTo(anotherBigCup));
        System.out.println("bigCup.compareTo(overwhelmingCup): " + bigCup.compareTo(overwhelmingCup));
    }
}

Результат выполнения кода:

bigCup.compareTo(hugeCup): -1
hugeCup.compareTo(bigCup): 1
bigCup.compareTo(anotherBigCup): 0
bigCup.compareTo(overwhelmingCup): -2

В текущей реализации compareTo() возвращает разность this.ordinal() - e.ordinal(), отсюда значение -2. Метод объявлен как final, поэтому естественный порядок констант всегда совпадает с порядком объявления.

Метод equals() и оператор ==

Метод equals() в Enum переопределяет метод из класса Object и объявлен как final. Две константы равны, только если это одна и та же константа одного и того же перечисления. Совпадение порядковых номеров у констант разных перечислений не делает их равными.

Поскольку каждая константа существует в единственном экземпляре, для сравнения можно использовать оператор == — результат будет тем же, что и у equals().

public class CoffeeSizeEquals {
    public static void main(String[] args) {
        CoffeeSize bigCup = CoffeeSize.BIG;
        CoffeeSize hugeCup = CoffeeSize.HUGE;
        CoffeeSize anotherBigCup = CoffeeSize.BIG;

        System.out.println("bigCup.equals(hugeCup): " + bigCup.equals(hugeCup));
        System.out.println("bigCup.equals(anotherBigCup): " + bigCup.equals(anotherBigCup));
        System.out.println("bigCup == anotherBigCup: " + (bigCup == anotherBigCup));
    }
}
bigCup.equals(hugeCup): false
bigCup.equals(anotherBigCup): true
bigCup == anotherBigCup: true

Совет

Для enum оператор == даже предпочтительнее equals(): он не выбросит NullPointerException, если слева null, а сравнение констант разных перечислений через == компилятор отклонит сразу.

Enum со значениями: поля, конструктор и методы

Создать экземпляр перечисления с помощью оператора new нельзя, но в остальном перечисление обладает возможностями обычного класса: в него можно добавлять конструкторы, поля и методы. Так получается enum со значениями.

Допустим, мы хотим задать объём чашки кофе в миллилитрах. Для этого добавим в перечисление поле ml, метод-геттер getMl() и конструктор, который принимает значение объёма.

Конструктор перечисления всегда private: если модификатор не указан, он подразумевается, а объявить конструктор как public или protected — ошибка компиляции. Как же тогда его вызвать? После имени константы ставятся круглые скобки с аргументами — так константа создаётся через нужный конструктор. Список констант должен идти первым в теле enum и заканчиваться точкой с запятой, если за ним следуют другие члены.

public enum CoffeeSize2 {
    // 100, 150 и 200 передаются в конструктор
    BIG(100), HUGE(150), OVERWHELMING(200);

    private final int ml;

    CoffeeSize2(int ml) {
        this.ml = ml;
    }

    public int getMl() {
        return ml;
    }
}

Методы перечисления вызываются так же, как и у обычного объекта. В следующем классе мы перебираем все константы и для каждой вызываем getMl():

public class CoffeeSizeMlExample {
    public static void main(String[] args) {
        for (CoffeeSize2 coffeeSize : CoffeeSize2.values()) {
            System.out.println(coffeeSize + " " + coffeeSize.getMl());
        }
    }
}
BIG 100
HUGE 150
OVERWHELMING 200

Конструкторы в перечислении можно перегружать. Чтобы вызвать конструктор без параметров, не пишите скобки после константы:

public enum CoffeeSize3 {
    BIG(100), HUGE, OVERWHELMING(200);

    private final int ml;

    CoffeeSize3(int ml) {
        this.ml = ml;
    }

    CoffeeSize3() {
        this.ml = -1;
    }

    public int getMl() {
        return ml;
    }
}

Где можно объявить перечисление

Перечисление можно объявить отдельным типом верхнего уровня (в своём файле) или как член класса. Начиная с Java 16 (JEP 395) перечисления можно объявлять и локально — внутри метода, конструктора или блока. Вложенные и локальные перечисления неявно static, поэтому не имеют доступа к полям экземпляра внешнего класса.

В этом примере перечисление CoffeeSize объявлено внутри класса Coffee3:

public class Coffee3 {
    enum CoffeeSize { BIG, HUGE, OVERWHELMING }

    CoffeeSize size;
}

Для обращения к такому перечислению из другого класса используется имя внешнего класса:

public class CoffeeTest2 {
    public static void main(String[] args) {
        Coffee3 drink = new Coffee3();
        drink.size = Coffee3.CoffeeSize.BIG; // имя внешнего класса необходимо
    }
}

Объявление перечисления внутри метода (Java 16+):

public class CoffeeTest3 {
    public static void main(String[] args) {
        enum CoffeeSize { BIG, HUGE, OVERWHELMING }
        CoffeeSize coffeeSize = CoffeeSize.BIG;
    }
}

Переопределение методов в константах

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

Добавим в перечисление метод getLidCode(), который возвращает код крышки для чашки кофе. Для всех констант подходит код B, кроме OVERWHELMING — для неё нужен код A. После объявления константы открываем фигурные скобки и переопределяем в них метод. Если нужно переопределить несколько методов, это делается в тех же фигурных скобках.

public enum CoffeeSize4 {
    BIG(100),
    HUGE(150),
    OVERWHELMING(200) {
        @Override
        public String getLidCode() {
            return "A";
        }
    };

    private final int ml;

    CoffeeSize4(int ml) {
        this.ml = ml;
    }

    public int getMl() {
        return ml;
    }

    public String getLidCode() {
        return "B";
    }
}
public class CoffeeSizeExample7 {
    public static void main(String[] args) {
        for (CoffeeSize4 coffeeSize : CoffeeSize4.values()) {
            System.out.println(coffeeSize + " " + coffeeSize.getLidCode());
        }
    }
}

Результат выполнения кода:

BIG B
HUGE B
OVERWHELMING A

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

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; }
    };

    public abstract int apply(int a, int b);
}

Неочевидный момент

Константа с телом — это экземпляр анонимного подкласса. Поэтому CoffeeSize4.OVERWHELMING.getClass() вернёт не CoffeeSize4, а класс вида CoffeeSize4$1. Чтобы получить сам тип перечисления, используйте getDeclaringClass().

Перечисления и интерфейсы

Перечисления не могут явно наследовать другие классы, но могут реализовывать любое количество интерфейсов. Методы интерфейса можно реализовать один раз для всего enum или отдельно в теле каждой константы:

public interface HasPrice {
    int priceInCents();
}

public enum Currency implements HasPrice {
    PENNY(1), NICKEL(5), DIME(10), QUARTER(25);

    private final int value;

    Currency(int value) {
        this.value = value;
    }

    @Override
    public int priceInCents() {
        return value;
    }
}
public class CurrencyExample {
    public static void main(String[] args) {
        HasPrice coin = Currency.QUARTER;
        System.out.println(coin.priceInCents()); // 25
    }
}

Так же можно реализовать и стандартные интерфейсы, например Runnable: тогда константу перечисления можно передать прямо в конструктор Thread.

EnumSet и EnumMap

Для работы с перечислениями в пакете java.util есть две специализированные коллекции. Они работают быстрее и занимают меньше памяти, чем HashSet и HashMap, потому что опираются на порядковые номера констант:

  • EnumSet — множество констант одного enum, внутри хранится как битовая маска;
  • EnumMap — отображение, ключами которого являются константы enum, внутри это массив, индексированный по ordinal().
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.Map;
import java.util.Set;

public class EnumCollectionsExample {
    public static void main(String[] args) {
        Set<CoffeeSize> large = EnumSet.of(CoffeeSize.HUGE, CoffeeSize.OVERWHELMING);
        System.out.println(large.contains(CoffeeSize.BIG)); // false

        Map<CoffeeSize, Integer> prices = new EnumMap<>(CoffeeSize.class);
        prices.put(CoffeeSize.BIG, 150);
        prices.put(CoffeeSize.HUGE, 200);
        System.out.println(prices); // {BIG=150, HUGE=200}
    }
}

Частые ошибки при работе с enum

  • Хранение ordinal() в базе данных или файле. Порядковый номер меняется, если добавить константу в середину или поменять порядок. Храните name() или собственное поле-код.
  • valueOf() для пользовательского ввода без проверки. Строка "big" или "BIG " приведёт к IllegalArgumentException. Нормализуйте ввод или обрабатывайте исключение.
  • Попытка написать new CoffeeSize() или public-конструктор. Оба варианта не компилируются.
  • Попытка написать enum A extends B. Перечисление уже наследует java.lang.Enum; вместо наследования используйте интерфейсы.
  • Изменяемые поля в enum. Константа одна на всё приложение, поэтому изменяемое поле фактически становится глобальным состоянием. Делайте поля final.

Часто задаваемые вопросы

Какой тип данных используется для перечисления в Java?

Каждое перечисление — это самостоятельный ссылочный тип данных, особый вид класса, который неявно наследует java.lang.Enum. Переменная типа enum хранит ссылку на одну из констант и может быть равна null. Внутри константы нумеруются целыми числами (ordinal()), но сам enum к типу int не приводится.

Чем enum отличается от интерфейса java.util.Enumeration?

Это разные вещи с похожими названиями. enum — ключевое слово для объявления перечисления. java.util.Enumeration — устаревший интерфейс для перебора элементов коллекций вроде Vector и Hashtable (методы hasMoreElements() и nextElement()); в новом коде вместо него используют Iterator.

Почему enum считают лучшим способом реализовать Singleton?

Перечисление с одной константой (enum Registry { INSTANCE; }) гарантирует единственный экземпляр: JVM создаёт его потокобезопасно при инициализации класса, рефлексия не может вызвать конструктор enum, а при десериализации возвращается та же константа. Такой подход рекомендует Джошуа Блох в книге «Effective Java».

Как правильно сохранять enum в базу данных?

Надёжнее всего хранить строку из name() или собственный стабильный код из поля перечисления. Число из ordinal() сохранять не стоит: после добавления или перестановки констант старые записи начнут указывать на другие значения. В JPA для этого используют @Enumerated(EnumType.STRING).

Можно ли унаследовать один enum от другого?

Нет. Перечисление уже неявно наследует java.lang.Enum, а множественного наследования классов в Java нет, поэтому extends у enum не пишется. Унаследоваться от enum тоже нельзя — он неявно final (подклассами бывают только анонимные тела констант). Общее поведение нескольких перечислений выносят в интерфейс.

Видео объяснение

Предпочитаете видеоформат? Посмотрите этот урок с примерами и объяснениями.

Комментарии

Зарегистрируйтесь или войдите, чтобы иметь возможность оставить комментарий.