Коллекции для перечислений
Когда ключи словаря или элементы множества — константы перечисления (enum), вместо HashMap и HashSet лучше взять EnumMap и EnumSet. Они быстрее, занимают меньше памяти и хранят константы в порядке их объявления. Само перечисление, его методы values(), valueOf() и ordinal() разобраны в уроке о перечислениях.
EnumMap
EnumMap — словарь, ключами которого служат константы одного перечисления. В конструктор передают класс перечисления, например Day.class:
enum Day {
MON, TUE, WED, THU, FRI, SAT, SUN
}
void main() {
Map<Day, String> plans = new EnumMap<>(Day.class);
plans.put(Day.FRI, "кино");
plans.put(Day.MON, "бассейн");
plans.put(Day.WED, "английский");
IO.println(plans);
}Результат:
{MON=бассейн, WED=английский, FRI=кино}Ключи идут в порядке объявления констант, а не в порядке добавления. Внутри EnumMap — обычный массив, поэтому поиск по ключу очень быстрый.
EnumSet
EnumSet — множество констант. Его не создают через new, а получают фабричными методами: of() — перечисленные константы, range() — все от одной до другой, allOf() — все, noneOf() — пустое множество:
enum Day {
MON, TUE, WED, THU, FRI, SAT, SUN
}
void main() {
Set<Day> weekend = EnumSet.of(Day.SAT, Day.SUN);
Set<Day> workdays = EnumSet.range(Day.MON, Day.FRI);
IO.println(weekend);
IO.println(workdays);
IO.println(workdays.contains(Day.SAT));
}Результат:
[SAT, SUN]
[MON, TUE, WED, THU, FRI]
falseВнутри EnumSet хранит константы как биты одного числа, как маски из урока о побитовых операторах, только код при этом остаётся читаемым.
Дополнение множества
EnumSet.complementOf() возвращает все константы, которых нет в данном множестве. Так удобно описывать «все, кроме»:
enum Day {
MON, TUE, WED, THU, FRI, SAT, SUN
}
void main() {
EnumSet<Day> busy = EnumSet.of(Day.MON, Day.WED, Day.FRI);
EnumSet<Day> free = EnumSet.complementOf(busy);
IO.println(free);
}Результат:
[TUE, THU, SAT, SUN]Подсчёт по константам
EnumMap хорошо подходит для подсчёта: ключ — константа, значение — счётчик. Метод merge() прибавляет единицу или создаёт запись, если её ещё нет:
enum Status {
NEW, PAID, SHIPPED
}
void main() {
List<Status> orders = List.of(Status.PAID, Status.NEW, Status.PAID, Status.SHIPPED, Status.PAID);
Map<Status, Integer> counts = new EnumMap<>(Status.class);
for (Status s : orders) {
counts.merge(s, 1, Integer::sum);
}
IO.println(counts);
}Результат:
{NEW=1, PAID=3, SHIPPED=1}Integer::sum — ссылка на метод, который складывает два числа: старое значение и единицу.
Поиск константы по строке
valueOf() падает, если такой константы нет. Когда строка приходит от пользователя, надёжнее перебрать values() и сравнить имена без учёта регистра:
enum Size {
SMALL, MEDIUM, LARGE
}
Size findSize(String text) {
for (Size s : Size.values()) {
if (s.name().equalsIgnoreCase(text)) {
return s;
}
}
return Size.MEDIUM;
}
void main() {
IO.println(findSize("large"));
IO.println(findSize("XXL"));
}Результат:
LARGE
MEDIUMЧастая ошибка
Номер ordinal() зависит от порядка констант в коде. Если сохранить его в файл или базу, а потом добавить новую константу в начало, старые номера начнут означать другое. Здесь 1 сохранили, когда перечисление было SMALL, MEDIUM, LARGE, а затем в начало добавили XS:
enum Size {
XS, SMALL, MEDIUM, LARGE
}
void main() {
int saved = 1;
IO.println(Size.values()[saved]);
}Результат:
SMALLВместо MEDIUM получился SMALL. Сохраняйте имя константы (name()) или собственное поле с кодом, а для данных по константам используйте EnumMap вместо массива с индексом ordinal().