Avatar ·

Что делает оператор ?: в Kotlin? (Elvis Operator)

Всем привет! Я недавно начал учить Kotlin и наткнулся на странный оператор `?:`. Видел его в чужом коде, но не могу понять, что он делает. Например, есть такой код: ```kotlin val name: String? = null val result = name ?: "Unknown" ``` Я понимаю, что `String?` означает, что переменная может быть null. Но что делает `?:`? Почему `result` становится "Unknown", хотя `name` — null? В Java я бы написал `if (name != null) name else "Unknown"`, но здесь всего один символ. Пожалуйста, объясните простыми словами, как работает этот оператор, и покажите примеры. Ещё интересно, можно ли его использовать не только с простыми значениями, но и с функциями? И есть ли какие-то подводные камни? Заранее спасибо! <<>>

Оператор Элвиса (Elvis Operator) в Kotlin

Оператор ?: называется Elvis Operator (оператор Элвиса) — из-за схожести с причёской Элвиса Пресли, если повернуть голову набок. Он используется для обработки null-значений и является краткой формой тернарного оператора, который есть в Java, но отсутствует в Kotlin.

Как он работает?

Оператор ?: проверяет выражение слева от себя. Если оно не равно null, то возвращается это значение. Если слева null, то возвращается значение справа. То есть:

val result = a ?: b
// Эквивалентно:
val result = if (a != null) a else b

Пример с вашим кодом

val name: String? = null
val result = name ?: "Unknown"
println(result) // Выведет: Unknown

val name2: String? = "Alice"
val result2 = name2 ?: "Unknown"
println(result2) // Выведет: Alice

Когда name равен null, оператор подставляет значение справа — "Unknown". Когда name2 не равен null, возвращается "Alice".

Использование с функциями и цепочками

Оператор Элвиса можно использовать с любыми выражениями, включая вызовы функций:

fun getDefaultName(): String {
    println("Вызвана getDefaultName()")
    return "Default"
}

val name: String? = null
val result = name ?: getDefaultName() // Вызовется getDefaultName()
println(result) // Default

val name2: String? = "Bob"
val result2 = name2 ?: getDefaultName() // getDefaultName() НЕ вызовется
println(result2) // Bob

Важно: выражение справа вычисляется лениво — только если слева null. Это повышает производительность.

Цепочки операторов

Можно объединять несколько ?: для последовательных fallback-значений:

val primary: String? = null
val secondary: String? = null
val fallback = "Final"

val result = primary ?: secondary ?: fallback
println(result) // Final

Сочетание с безопасным вызовом (?.)

Очень часто ?: используется вместе с оператором безопасного вызова ?.:

data class User(val name: String?)
fun getUser(): User? = null

val userName = getUser()?.name ?: "Guest"
println(userName) // Guest

Здесь getUser() возвращает null, поэтому ?.name тоже даёт null, и срабатывает ?:.

Подводные камни

  • Тип результата: Если слева не-null тип, а справа другой тип, Kotlin выведет общий тип. Например, val x: Any = "str" ?: 42 — тип будет Any.
  • Не путать с Java: В Java ?: нет. Вместо него используют if-else или Optional.ofNullable(...).orElse(...).
  • Нельзя использовать для присваивания в var с умным приведением: var x: String? = null; x ?: "default" — это выражение, оно не меняет x.
  • Правая часть может быть блоком: val result = x ?: run { println("fallback"); "default" }.

Заключение

Оператор Элвиса — элегантный способ обработки null в Kotlin. Он делает код чище и читаемее, заменяя громоздкие if-else конструкции. Используйте его везде, где нужно предоставить значение по умолчанию для nullable-переменной.

<<>>

Альтернативный взгляд: оператор Элвиса как частный случай when

Хотя оператор ?: очень удобен, важно понимать, что это не единственный способ обработки null. В некоторых ситуациях лучше использовать when или if-else для большей гибкости.

Например, если вам нужно не просто подставить значение, а выполнить разные действия в зависимости от null:

val name: String? = null

// С оператором Элвиса — только подстановка значения
val greeting = "Hello, ${name ?: "Guest"}!"

// С when — можно выполнить разную логику
val greeting2 = when (name) {
    null -> "Hello, Guest! (anonymous)"
    "Admin" -> "Hello, Admin! (privileged)"
    else -> "Hello, $name! (regular user)"
}

Когда стоит избегать ?:

  • Если правая часть сложная или имеет побочные эффекты — лучше использовать if-else для ясности.
  • Если вам нужно обработать несколько разных случаев — when будет читаемее.
  • Если вы работаете с коллекциями и хотите не только подставить значение, но и отфильтровать null — используйте filterNotNull() или mapNotNull().

Пример с коллекциями:

val list = listOf("Alice", null, "Bob", null, "Charlie")
// Вместо цепочки ?: лучше отфильтровать
val nonNull = list.filterNotNull() // ["Alice", "Bob", "Charlie"]

Оператор Элвиса — мощный инструмент, но не злоупотребляйте им. Иногда явный if-else или when делает код понятнее для других разработчиков.