В чём разница между !! и ?.?

В Kotlin !! и ?. — это два оператора, предназначенных для работы с nullable-типами, но они противоположны по поведению и цели.

!! — Not-null assertion operator

Оператор !! говорит компилятору: «Я точно знаю, что это значение не null. Убери проверку». Если значение окажется null, произойдёт NullPointerException.

Синтаксис:

val name: String? = null
val length = name!!.length // Бросит NullPointerException

Где применяется:

  • Когда ты на 100% уверен, что переменная не null (возможно, по логике до этого момента).

  • Когда хочется быстро «заставить» nullable-значение вести себя как ненулевое (не рекомендуется без особой необходимости).

  • Иногда в тестах, прототипах, при переходе от Java.

Поведение:

  • null → ❌ выброс исключения.

  • ненулевое значение → ✅ работает как обычная переменная.

?. — Safe call operator

Оператор ?. используется для безопасного обращения к свойствам или методам объекта, который может быть null. Если значение не null, операция выполняется. Если значение null, результат — тоже null, и вызов не происходит.

Синтаксис:

val name: String? = null
val length = name?.length // length будет null, но без исключения

Где применяется:

  • Когда ты не уверен, есть ли значение, и хочешь избежать NullPointerException.

  • Для цепочки вызовов (?. можно ставить подряд).

  • Вместе с ?: (Elvis-оператором) — для установки значений по умолчанию.

Поведение:

  • null → возвращается null.

  • ненулевое значение → результат выражения.

Табличка сравнения

Оператор Назначение При null При not null Возвращает
!! Принудительно обращаться как к not-null NPE (ошибка) Значение Тот же тип без ?
--- --- --- --- ---
?. Безопасно вызвать метод/свойство null Результат Nullable-результат
--- --- --- --- ---

Примеры сравнения

Пример 1:

val name: String? = "Kotlin"
val upper = name!!.uppercase() // OK: "KOTLIN"
val name: String? = null
val upper = name!!.uppercase() // Бросит NullPointerException

Пример 2:

val name: String? = "Kotlin"
val upper = name?.uppercase() // OK: "KOTLIN"
val name: String? = null
val upper = name?.uppercase() // OK: null

Почему !! опасен

Использование !! — это потенциально опасное действие. Оно убирает защиту от null, которую Kotlin внедряет на уровне типов.

fun printLength(s: String?) {
println(s!!.length) // Если s == null  будет NPE
}

Такие ошибки могут проявиться в рантайме, а не на этапе компиляции.

Альтернатива !! — безопасные конструкции

С ?. и ?:

val name: String? = null
val length = name?.length ?: 0 // Безопасно, результат: 0

С let

name?.let {
println(it.length)
}

С if (name != null)

if (name != null) {
println(name.length)
}

Пример с ошибкой и альтернативой

❌ Ошибка:

val user: User? = getUser()
val city = user!!.address!!.city!!.name // Много !!  высокая вероятность NPE

✅ Безопасно:

val city = user?.address?.city?.name ?: "Unknown"

Оператор !! — это костыль, который лучше избегать. Оператор ?. — инструмент, встроенный в философию Kotlin, помогающий писать безопасный и предсказуемый код.