Введение: Прощай, switch-case? Да здравствует when!
Если вы пришли в Kotlin из мира Java, C# или JavaScript, то первое, что вы, вероятно, ищете — это привычный оператор switch-case. И тут вас ждёт сюрприз: в Kotlin нет классического switch-case. Вместо него язык предлагает гораздо более мощный и гибкий инструмент — when.
На Stack Overflow вопрос "How to implement switch-case statement in Kotlin" собирает тысячи просмотров. Это нормально: разработчики привыкают к одним конструкциям и хотят найти им аналоги. Но правда в том, что when в Kotlin — это не просто замена switch. Это его эволюция, которая под капотом работает умнее и быстрее.
В этой статье мы не просто покажем, как писать when. Мы заглянем под капот: узнаем, во что компилируется ваш код, почему он быстрее switch в некоторых сценариях и как использовать when на 100%.
1. Базовый синтаксис: от switch-case к when
Для начала — самый простой пример. Представим, что у нас есть старая добрая Java-конструкция:
// Java
int day = 3;
String dayName;
switch (day) {
case 1:
dayName = "Понедельник";
break;
case 2:
dayName = "Вторник";
break;
case 3:
dayName = "Среда";
break;
default:
dayName = "Неизвестный день";
}
В Kotlin это будет выглядеть так:
// Kotlin
val day = 3
val dayName = when (day) {
1 -> "Понедельник"
2 -> "Вторник"
3 -> "Среда"
else -> "Неизвестный день"
}
Уже тут видны два главных преимущества:
- Нет break — Kotlin сам выходит из блока после выполнения ветки.
- Возвращает значение — можно присвоить результат переменной.
2. Как работает when под капотом: байт-код и оптимизация
Теперь самое интересное. Когда вы пишете when, компилятор Kotlin анализирует ваши условия и выбирает оптимальный способ выполнения. Заглянем в байт-код (примерно, без лишних деталей).
Случай 1: when с константами (как switch-case)
Если вы используете when с целыми числами, строками или enum-константами, компилятор генерирует таблицу переходов (tableswitch или lookupswitch). Это тот же самый механизм, который использует Java для switch-case. Разница в том, что Kotlin делает это автоматически, без вашего участия.
Случай 2: when с любыми условиями
Если вы пишете что-то вроде:
when (x) {
in 1..10 -> "Маленькое"
in 11..100 -> "Среднее"
else -> "Большое"
}
Компилятор не может использовать таблицу переходов. Он генерирует цепочку if-else. Но Kotlin делает это максимально эффективно: условия проверяются по порядку, и как только находится совпадение — выполнение прекращается.
Случай 3: when без аргумента
Это вообще отдельная магия. Если вы пишете:
when {
x > 0 -> "Положительное"
x < 0 -> "Отрицательное"
else -> "Ноль"
}
Компилятор превращает это в обычные if-else. Но синтаксис гораздо чище, чем вложенные if.
Вывод: Kotlin не просто копирует switch-case. Он умно выбирает между таблицей переходов и if-else, чтобы ваш код работал максимально быстро.
3. Продвинутые возможности when: то, чего нет в switch
when умеет гораздо больше, чем просто сравнивать значения. Вот несколько killer-фич:
3.1. Проверка типов (smart cast)
fun describe(obj: Any): String = when (obj) {
is String -> "Строка длиной ${obj.length}" // smart cast к String
is Int -> "Целое число"
is List<*> -> "Список из ${obj.size} элементов"
else -> "Неизвестный тип"
}
3.2. Диапазоны и коллекции
val score = 85
val grade = when (score) {
in 90..100 -> "Отлично"
in 75..89 -> "Хорошо"
in 60..74 -> "Удовлетворительно"
else -> "Неудовлетворительно"
}
3.3. Несколько значений в одной ветке
val letter = 'A'
val type = when (letter) {
'A', 'E', 'I', 'O', 'U' -> "Гласная"
'Y' -> "Иногда гласная"
else -> "Согласная"
}
3.4. when без аргумента (замена if-else цепочки)
val result = when {
x % 15 == 0 -> "FizzBuzz"
x % 3 == 0 -> "Fizz"
x % 5 == 0 -> "Buzz"
else -> x.toString()
}
4. Производительность: when vs switch-case vs if-else
Многие разработчики переживают: не будет ли when медленнее классического switch? Ответ: нет, не будет.
Мы провели небольшое исследование (на реальном коде):
- when с константами — компилируется в ту же tableswitch, что и Java switch. Производительность идентична.
- when с условиями — чуть медленнее таблицы, но быстрее, чем ручная цепочка if-else, из-за оптимизации компилятора.
- if-else цепочка — самая медленная, особенно если много веток.
Совет: Используйте when везде, где у вас больше 2-3 веток. Код станет чище, а производительность не пострадает.
5. Частые ошибки и как их избежать
Ошибка 1: Забыть про else
// Ошибка компиляции, если when используется как выражение
val result = when (x) {
1 -> "Один"
2 -> "Два"
// Нет else — компилятор ругается
}
Решение: Всегда добавляйте else, если when возвращает значение.
Ошибка 2: Использовать when там, где нужен if
// Плохо
when {
x > 0 -> println("Положительное")
}
// Хорошо
if (x > 0) println("Положительное")
Ошибка 3: Путать when с аргументом и без
// С аргументом