Kotlin guide done. (20.10.26)

This commit is contained in:
2026-10-20 12:54:25 +03:00
parent da3ad9dd1a
commit 6853ada348
81 changed files with 30112 additions and 0 deletions
@@ -0,0 +1,298 @@
---
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<Describable> = 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<Status>` | `EnumEntries<Status>` (это `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, Int>(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)