--- title: Enum class description: Перечисления со свойствами и методами, entries, when по enum и его границы tags: - kotlin - oop --- `enum class` - класс с фиксированным набором экземпляров. ```kotlin enum class Status { ACTIVE, BLOCKED, DELETED } fun main() { val s = Status.ACTIVE println(s) // ACTIVE println(s.name) // ACTIVE — строка println(s.ordinal) // 0 — позиция в объявлении } ``` ## Свойства и конструктор Каждая константа - вызов конструктора: ```kotlin enum class Planet(val radiusKm: Int, val hasRings: Boolean) { EARTH(6371, false), SATURN(58232, true), MARS(3390, false); val radiusM: Int get() = radiusKm * 1000 fun describe() = "$name: радиус $radiusKm км, кольца=$hasRings" } fun main() { println(Planet.SATURN.describe()) // SATURN: радиус 58232 км, кольца=true println(Planet.EARTH.radiusM) // 6371000 } ``` Точка с запятой после последней константы обязательна, если дальше идёт тело класса. ## Методы в константах Константа может переопределить метод - это заменяет `when` по самой себе: ```kotlin enum class Operation { PLUS { override fun apply(a: Int, b: Int) = a + b }, MINUS { override fun apply(a: Int, b: Int) = a - b }, TIMES { override fun apply(a: Int, b: Int) = a * b }; abstract fun apply(a: Int, b: Int): Int } fun main() { println(Operation.PLUS.apply(2, 3)) // 5 println(Operation.TIMES.apply(2, 3)) // 6 println(Operation.entries.joinToString { "${it.name}=${it.apply(6, 3)}" }) // PLUS=9, MINUS=3, TIMES=18 } ``` Каждая такая константа компилируется в анонимный подкласс. ## Реализация интерфейса ```kotlin interface Describable { fun describe(): String } enum class Level(val weight: Int) : Describable { LOW(1), HIGH(10); override fun describe() = "$name (вес $weight)" } fun main() { val items: List = Level.entries println(items.joinToString { it.describe() }) // LOW (вес 1), HIGH (вес 10) } ``` ## entries, values, valueOf ```kotlin enum class Status { ACTIVE, BLOCKED } fun main() { println(Status.entries) // [ACTIVE, BLOCKED] — список println(Status.entries.size) // 2 println(Status.valueOf("ACTIVE")) // ACTIVE println(Status.entries.first().name) // ACTIVE // println(Status.valueOf("НЕТ")) // IllegalArgumentException } ``` `entries` (Kotlin 1.9+) заменяет `values()`. Разница практическая: | | `values()` | `entries` | |---|---|---| | тип | `Array` | `EnumEntries` (это `List`) | | аллокация | **новый массив при каждом вызове** | один неизменяемый список | | изменяемость | элементы можно подменить | нельзя | ```kotlin enum class Status { ACTIVE, BLOCKED } fun main() { @Suppress("DEPRECATION") println(Status.values().contentToString()) // [ACTIVE, BLOCKED] println(Status.entries) // [ACTIVE, BLOCKED] } ``` В новом коде всегда `entries`. Безопасный разбор строки: ```kotlin enum class Status { ACTIVE, BLOCKED } fun parseStatus(s: String): Status? = Status.entries.find { it.name == s } fun main() { println(parseStatus("ACTIVE")) // ACTIVE println(parseStatus("нет")) // null — без исключения } ``` Через `enumValueOf` то же самое, но с исключением. Для внешних данных (JSON, БД, параметры запроса) нужна версия с `null`. ## when по enum Главное применение. Компилятор проверяет полноту, и `else` не нужен: ```kotlin enum class Status { ACTIVE, BLOCKED, DELETED } fun message(s: Status): String = when (s) { Status.ACTIVE -> "работает" Status.BLOCKED -> "заблокирован" Status.DELETED -> "удалён" } fun main() { println(message(Status.BLOCKED)) // заблокирован } ``` Если добавить константу `ARCHIVED`, этот `when` перестанет компилироваться - компилятор найдёт все места, которые нужно обновить. Поэтому `else` в `when` по enum - вредная привычка: он скрывает именно те ошибки, от которых enum защищает. Импорт констант сокращает запись: ```kotlin enum class Status { ACTIVE, BLOCKED } fun message(s: Status): String { return when (s) { Status.ACTIVE -> "работает" Status.BLOCKED -> "заблокирован" } } fun main() = println(message(Status.ACTIVE)) ``` ## Сравнение и порядок ```kotlin enum class Level { LOW, MEDIUM, HIGH } fun main() { println(Level.LOW == Level.LOW) // true println(Level.LOW < Level.HIGH) // true — по ordinal println(Level.entries.sorted()) // [LOW, MEDIUM, HIGH] println(Level.HIGH.ordinal) // 2 println(Level.entries.maxOf { it.ordinal }) // 2 } ``` `enum` реализует `Comparable` по `ordinal`. Сравнивать через `==` безопасно: экземпляр единственный, поэтому `==` и `===` дают одно и то же. ## EnumMap и EnumSet Специализированные коллекции для enum - массив вместо хеш-таблицы: ```kotlin import java.util.EnumMap import java.util.EnumSet enum class Day { MON, TUE, WED } fun main() { val hours = EnumMap(Day::class.java) hours[Day.MON] = 8 hours[Day.TUE] = 6 println(hours) // {MON=8, TUE=6} val workdays = EnumSet.of(Day.MON, Day.TUE) println(workdays.contains(Day.WED)) // false println(EnumSet.complementOf(workdays)) // [WED] } ``` Это классы JDK; в stdlib Kotlin аналогов нет. Для небольших enum разница с `HashMap` несущественна. ## companion object ```kotlin enum class Status(val code: Int) { ACTIVE(1), BLOCKED(2); companion object { private val byCode = entries.associateBy { it.code } fun fromCode(code: Int): Status? = byCode[code] } } fun main() { println(Status.fromCode(2)) // BLOCKED println(Status.fromCode(99)) // null } ``` Поиск по коду через заранее построенную `Map` - стандартный приём. Подробнее: [Object и companion object](Object%20и%20companion%20object.md) ## Когда enum, а когда sealed ```kotlin enum class Status { OK, ERROR } // варианты без данных sealed interface Result2 data class Ok(val value: Int) : Result2 // варианты с разными данными data class Error(val message: String) : Result2 ``` `enum` - фиксированный набор **значений**, у всех одинаковая структура. `sealed` - фиксированный набор **типов**, у каждого свои свойства. Подробнее: [Sealed class и interface](Sealed%20class%20и%20interface.md) > [!info] Отличия от Java > | Java | Kotlin | > |---|---| > | `enum Status { ACTIVE }` | `enum class Status { ACTIVE }` | > | `Status.values()` | `Status.entries` | > | `Status.valueOf(s)` | `Status.valueOf(s)` или `entries.find` | > | `switch` без проверки полноты до Java 21 | `when` проверяет всегда | > | `EnumMap`/`EnumSet` | то же (из JDK) | > > Устройство одинаковое - на JVM это тот же `java.lang.Enum`. Два практических различия: > > **`entries` вместо `values()`.** Java-метод `values()` возвращает новый массив при каждом вызове, потому что массив изменяем и вернуть общий нельзя. `entries` возвращает неизменяемый список, поэтому экземпляр один. В цикле это заметная разница. > > **Проверка полноты.** В Java `switch` по enum без `default` долго не проверялся: добавление константы молча менялo поведение. Kotlin проверяет `when`-выражение с момента появления языка, а с Kotlin 2.0 - и `when`-инструкцию. > > Сравнить: [Enum в Java](../../../Программирование%20на%20Java/Синтаксис%20и%20типы/Базовые%20типы%20ООП/Enum.md) > [!warning] Подводные камни > **`ordinal` нельзя сохранять в БД или в файл.** Он меняется при вставке константы в середину: > ```kotlin > enum class Status { ACTIVE, BLOCKED } // BLOCKED.ordinal == 1 > // добавили NEW в середину: > // enum class Status { ACTIVE, NEW, BLOCKED } // BLOCKED.ordinal == 2 > ``` > Все сохранённые единицы станут означать `NEW`. Для сериализации используйте `name` или свой стабильный `code`. > > **`name` тоже хрупок при переименовании** - но его хотя бы нельзя изменить случайно. Самый надёжный вариант - явное свойство: > ```kotlin > enum class Status(val code: String) { > ACTIVE("active"), BLOCKED("blocked") > } > > fun main() { println(Status.ACTIVE.code) } // active > ``` > > **`valueOf` бросает исключение на незнакомой строке**, а не возвращает `null`. Для внешних данных нужна своя обёртка через `entries.find`. > > **`values()` аллоцирует массив на каждый вызов:** > ```kotlin > enum class Status { ACTIVE } > > fun main() { > @Suppress("DEPRECATION") > repeat(3) { Status.values() } // три разных массива > repeat(3) { Status.entries } // один и тот же список > println("ok") > } > ``` > > **`else` в `when` по enum отключает проверку полноты.** Самая частая ошибка: добавили константу, компилятор промолчал, в рантайме сработала ветка `else`. > > **Enum нельзя наследовать и расширять.** Если набор вариантов должен быть расширяемым (например, плагинами), enum не подходит - нужен `sealed interface` или обычная иерархия. > > **Enum с изменяемым состоянием - глобальная переменная:** > ```kotlin > enum class Counter { > INSTANCE; > var count = 0 // общее состояние на всю программу > } > > fun main() { > Counter.INSTANCE.count++ > println(Counter.INSTANCE.count) // 1 — и так во всех потоках > } > ``` > Константы enum - синглтоны, поэтому `var` в них небезопасен при многопоточности. > > **`when` по nullable enum всё равно требует `else` или ветки `null`:** > ```kotlin > enum class Status { ACTIVE, BLOCKED } > > fun message(s: Status?): String = when (s) { > Status.ACTIVE -> "работает" > Status.BLOCKED -> "заблокирован" > null -> "не задан" > } > > fun main() { println(message(null)) } // не задан > ``` Дополнительно: [Sealed class и interface](Sealed%20class%20и%20interface.md) Далее: [Sealed class и interface](Sealed%20class%20и%20interface.md)