История вопроса:
Оператор Elvis (?:) появился в Kotlin как лаконичный и безопасный способ обработки nullable-значений. Он получил своё название из-за схожести с причёской Элвиса Пресли, если смотреть на оператор сбоку. Цель — избавиться от шаблонного кода вида if (a != null) a else b и сделать повседневную разработку более удобной.
Проблема:
Прямое обращение к значению, которое может быть null, приводит к ошибке времени выполнения (NullPointerException). Поэтому необходим инструмент, позволяющий элегантно подставлять значения по умолчанию, когда переменная оказывается null.
Решение:
Elvis-оператор применим к nullable-типам. Если выражение слева от оператора не равно null, возвращается оно, иначе — выражение справа. Это делает код более компактным и безопасным.
Пример кода:
fun getLength(str: String?): Int { return str?.length ?: 0 } val result = getLength(null) // result == 0 val result2 = getLength("Hello") // result2 == 5
Ключевые особенности:
Что будет, если справа от Elvis-оператора выражение с побочным эффектом? Срабатывает оно всегда?
Нет! Выражение справа вычисляется только если слева оказался null. Это может играть важную роль, если функция имеет побочные эффекты или "дорогостоящие" вычисления.
Пример кода:
var called = false val x: String? = "test" val y = x ?: run { called = true; "default" } // called будет равен false, потому что run даже не выполнится
Можно ли использовать Elvis-оператор для выбрасывания исключений?
Да, в Kotlin можно писать так:
fun getLengthStrict(str: String?): Int = str?.length ?: throw IllegalArgumentException("str is null")
Если str равен null, будет выброшено исключение. Это удобный механизм валидации данных.
Можно ли "склеивать" несколько Elvis-операторов подряд?
Да, это обычный подход для fallback'ов:
val name = fromDatabase ?: fromCache ?: "Unknown"
Это выражение вернёт первое НЕ null значение или строку "Unknown".
Многоуровневая вложенность null-check через Elvis:
val title = a?.b?.c?.d?.e ?: defaultTitle
Плюсы:
Минусы:
Явная декомпозиция по шагам, понятные имена переменных:
val deepValue = a?.b val deeperValue = deepValue?.c?.d ?: default
Плюсы:
Минусы: