Котлин, нулли и двойной восклицательный знак: знакомство
Вы когда-нибудь писали на Kotlin, компилятор ругался на потенциальный null, а вы в ответ ставили два восклицательных знака — !! — и шли дальше? Если да, то вы уже знакомы с героем нашей статьи — оператором double bang (он же оператор !! в Kotlin).
Этот символ — одновременно спаситель и опасный инструмент. Он говорит компилятору: «Заткнись, я знаю, что делаю, эта переменная точно не null». Но если вы ошиблись — приложение упадёт с NullPointerException. Звучит страшно? На самом деле, это просто мощный механизм, который нужно понимать и использовать с умом.
В этом руководстве мы разберём, что такое double bang Kotlin, как он работает, где его применять, а где лучше поискать альтернативы. Вы узнаете, как не выстрелить себе в ногу и писать надёжный код.
Проблема, которую решает !! : Null Safety в Kotlin
Kotlin создавался как язык, который должен был убить NullPointerException (NPE). И у него это почти получилось. В Kotlin типы по умолчанию не могут быть null. Вы просто не сможете присвоить null переменной типа String — компилятор не даст.
var name: String = "Алиса"
name = null // Ошибка компиляции!
Но реальный мир полон неопределённости. Данные приходят из базы, от пользователя, из сети. Поэтому Kotlin ввёл nullable-типы — типы, которые могут быть null. Они обозначаются вопросительным знаком: String?.
var nullableName: String? = "Боб"
nullableName = null // Теперь можно
И тут начинается магия. Вы не можете просто взять и вызвать метод у nullable-переменной:
val length = nullableName.length // Ошибка! "Only safe (?.) or non-null asserted (!!.) calls are allowed"
Kotlin заставляет вас обрабатывать null. Это гениально — вы не забываете проверить. Но иногда вы точно знаете, что значение не null. И вот тут на сцену выходит оператор двойного восклицательного знака Kotlin.
Что такое оператор !! (double bang)?
Оператор !! в Kotlin — это оператор принудительного утверждения non-null. Он берёт nullable-значение и преобразует его в non-null тип. Если значение оказалось null — выбрасывается KotlinNullPointerException.
Синтаксически он выглядит как два восклицательных знака после переменной:
val nonNullValue = nullableValue!!
Как это работает на примере
Представьте, что у вас есть функция, которая ищет пользователя по ID. Она возвращает User?, потому что пользователя может не быть. Но вы точно знаете, что в вашей тестовой среде пользователь с ID = 1 существует.
data class User(val name: String)
fun findUser(id: Int): User? {
// Какая-то логика поиска
return if (id == 1) User("Анна") else null
}
fun main() {
val user = findUser(1)
// Мы уверены, что user не null
val userName = user!!.name
println(userName) // Выведет: Анна
}
В этом примере double bang Kotlin сработал корректно, и мы получили имя. Но если бы ID был 2, приложение упало бы с исключением.
Когда использовать !! (и когда категорически нельзя)
Оператор !! (double bang) — это как красная кнопка. Её можно нажимать, но только если вы на 146% уверены. Давайте разберём легитимные сценарии.
Когда можно (и нужно) использовать !!
- Взаимодействие с Java-кодом. Java не знает о nullable-аннотациях. Если Java-метод возвращает объект, Kotlin видит его как
Platform Type(например,String!). Вы можете обращаться с ним как угодно. Но если вы уверены, что Java-метод не вернёт null — используйте!!. - В тестах. В unit-тестах вы часто создаёте объекты вручную и точно знаете, что они не null.
!!здесь уместен, чтобы не писать лишние проверки. - В конструкторах Android View Binding. Типичный код:
binding!!.textView.text = "Привет". Вы уверены, что binding не null, потому что он инициализирован вonCreateView. - Когда вы только что проверили на null. Иногда компилятор не может "догнать" логику, хотя она очевидна разработчику.
val list: List<String>? = getList()
if (list != null) {
// Здесь list smart-cast в List<String>, можно без !!
println(list.size)
} else {
// А вот здесь мы точно знаем, что list не null, но компилятор не в курсе
// Используем !! как "я знаю лучше"
val fallbackList = emptyList<String>()
println(fallbackList!!.size) // Сомнительно, но можно
}
Когда НЕЛЬЗЯ использовать !!
- В пользовательском вводе. Пользователь может ввести что угодно. Никогда не используйте
!!для данных, которые пришли от пользователя, из сети или из базы данных. - В публичных API. Если вы пишете библиотеку или функцию, которую будут вызывать другие — не используйте
!!. Вы не знаете, какие данные передадут. - В цепочках вызовов.
user!!.address!!.city!!.name— это катастрофа. Одна ошибка — и всё упадёт. Используйте?.letили?:. - Для "красивости". Если вы ставите
!!просто чтобы компилятор замолчал — вы делаете что-то не так. Потратьте 5 минут на нормальную обработку null.
Альтернативы оператору !!
Kotlin предлагает несколько элегантных способов работы с nullable-типами, которые безопаснее, чем double bang Kotlin. Давайте сравним.
1. Безопасный вызов (?. )
val length = nullableName?.length // Если nullableName null, length будет null
Идеально для цепочек: user?.address?.city. Если что-то null — цепочка обрывается, возвращается null.
2. Оператор Элвиса (?: )
val name = nullableName ?: "Гость" // Если null, используем значение по умолчанию