Перечисления enum
Представьте маленькую колоду из четырёх карт: валет, дама, король и туз. Колоду напечатали на фабрике, и пятую карту дома не допечатать. У каждой карты своё название, а в коробке они лежат по порядку.
Как хранить такую карту в программе? Можно строкой: String card = "king";. Но тогда компилятор пропустит и опечатку "kign", и карту "joker", которой в колоде нет. Число вроде int card = 2; не лучше: по коду не понять, что значит 2, а card = 9 тоже скомпилируется. Нужен тип, у которого есть только перечисленные значения:
public class Main {
public static void main(String[] args) {
Card c = Card.KING;
System.out.println(c); // KING
System.out.println(c.ordinal()); // 2
System.out.println(c == Card.KING); // true
for (Card x : Card.values()) {
System.out.print(x + " "); // JACK QUEEN KING ACE
}
}
}
enum Card { JACK, QUEEN, KING, ACE }Давайте разберём программу по шагам:
enum Card { … }послеMainобъявляет типCardровно с четырьмя значениями. Их имена пишут заглавными буквами через запятую, как имена констант.Card c = Card.KING;— к значению обращаются через имя типа и точку.println(c)печатает имя значения:KING.c.ordinal()возвращает номер значения в объявлении, считая с нуля: уJACKномер0, уQUEEN—1, уKING—2.c == Card.KINGдаётtrue.- Цикл for-each перебирает массив
Card.values()и печатает все карты по порядку.
Что такое enum
Перечисление — тип, все значения которого перечислены в его объявлении. Объявляют его словом enum, а значения называют константами перечисления. Каждая константа — объект типа Card, и Java создаёт его ровно один раз.
Что из этого следует:
- новых значений не бывает: на
new Card()компилятор ответитenum classes may not be instantiated; - переменной типа
Cardнельзя присвоить строку или число, только одну из четырёх констант илиnull; - две переменные со значением
Card.KINGвсегда ведут к одному объекту.
На собеседовании спрашивают, можно ли сравнивать перечисления через ==. Можно: каждая константа существует в одном экземпляре, и == даёт тот же ответ, что и equals.
Здесь картинка с колодой неточна. Колод можно напечатать сколько угодно, и в каждой будет свой король. Константа Card.KING в программе одна на всех: её не копируют и не печатают заново.
name, ordinal и values
У каждого перечисления есть готовые методы:
name()возвращает имя константы, как оно написано в коде:"KING";printlnпечатает то же имя;ordinal()возвращает номер константы в объявлении, считая с нуля;Card.values()возвращает массив всех констант в порядке объявления.
Номер зависит только от порядка в объявлении: переставьте константы, и номера изменятся. Поэтому ordinal() не берут как постоянный код значения — например, чтобы сохранить его в файл.
enum в switch
Перечисление удобно разбирать в switch. В метках case константы пишут без имени типа. Посчитаем очки карты:
int points = switch (c) {
case JACK -> 2;
case QUEEN -> 3;
case KING -> 4;
case ACE -> 11;
};
System.out.println(points); // 4Как вы уже знаете, switch-выражение должно дать значение для любого входа. Здесь перечислены все четыре константы, поэтому default не нужен. Если одну забыть, компилятор ответит the switch expression does not cover all possible input values. Так он подскажет и тогда, когда в колоду добавят новую карту.
Поле и конструктор
Перечисление — тот же класс, только с заранее известными объектами. Поэтому у констант могут быть поля, конструктор и методы. Очки можно хранить прямо в каждой карте:
public class Main {
public static void main(String[] args) {
System.out.println(Card.ACE.points); // 11
}
}
enum Card {
JACK(2), QUEEN(3), KING(4), ACE(11);
final int points;
Card(int points) {
this.points = points;
}
}Разберём объявление:
JACK(2)— константа с аргументом: для неё Java вызывает конструкторCard(2).- После последней константы стоит точка с запятой, а дальше идут поле и конструктор.
final int points;— у каждой карты своё число очков, и после конструктора оно не меняется.Card.ACE.pointsдаёт11.
Конструктор перечисления вызывает только сама Java, по одному разу для каждой константы. Теперь новая карта приходит в колоду вместе со своими очками, и забыть их не получится.
Частая ошибка
Константу сравнили со строкой:
Card c = Card.KING;
if (c.equals("KING")) { // ✗
System.out.println("king");
}Программа ничего не печатает и не сообщает об ошибке. c — объект типа Card, а "KING" — строка, и equals для объектов разных типов даёт false. Запись c == "KING" компилятор бы отверг с ошибкой incomparable types: Card and String, а вызов equals пропускает без ошибки. Сравнивайте с константой: c == Card.KING. Если на входе строка, сравнивайте с ней имя: c.name().equals("KING").
Главное
enumобъявляет тип с заранее перечисленными значениями; новых черезnewне создать.- Каждая константа существует в одном экземпляре, поэтому перечисления сравнивают через
==. name()иprintlnдают имя константы,ordinal()— номер с нуля,values()— массив всех констант.- switch-выражение по перечислению без
defaultдолжно перечислить все константы; поля и конструктор задают данные каждой константы.