Null-безопасность
Как система типов убирает NullPointerException — nullable-типы, safe call и оператор Элвиса, not-null assertion, умные приведения и условия их работы, lateinit и тип Nothing.
7 вопросов
JuniorТеорияОчень частоЧто помечает ? в String? и что на самом деле делает !!?
Что помечает ? в String? и что на самом деле делает !!?
Nullability — часть типа: String и String? — разные типы, и компилятор не пропустит null туда, где ожидается non-null тип. !! ничего не преобразует — он прямо на месте бросает NullPointerException, если значение оказалось null.
Типичные ошибки
- ✗Называть
!!преобразованием, а не утверждением «брось, если null» - ✗Считать
StringиString?одним типом с флагом - ✗Принимать проверку компилятора за предупреждение линтера, а не за ошибку
Уточняющие вопросы
- →Где
!!действительно оправдан и что вы написали бы вместо него? - →Как nullable-тип Kotlin выглядит для вызывающего его кода на Java?
JuniorТеорияЧастоКогда lateinit var уместнее, чем nullable var?
Когда lateinit var уместнее, чем nullable var?
lateinit подходит non-null значению, которое задают после конструктора, — внедрённому полю. Тип остаётся non-null, поэтому чтению не нужен ?.; раннее чтение бросает UninitializedPropertyAccessException. Nullable var нужен, когда отсутствие реально.
Типичные ошибки
- ✗Вешать
lateinitнаvalили на примитивный тип - ✗Путать
lateinitсby lazy, который вычисляет при первом чтении - ✗Ждать, что раннее чтение отдаст
null, а не бросит исключение
Уточняющие вопросы
- →Как проверить, было ли
lateinit-свойство уже инициализировано? - →Почему свойство примитивного типа
Intнельзя объявитьlateinit?
JuniorТеорияЧастоКак safe call ?. и оператор Элвиса ?: работают вместе?
Как safe call ?. и оператор Элвиса ?: работают вместе?
?. вызывает член только на non-null получателе; иначе всё выражение сразу даёт null, поэтому a?.b?.c обрывается на первом отсутствующем звене. ?: подставляет запасное значение — в a?.b ?: d правая часть вычисляется, только когда левая равна null.
Типичные ошибки
- ✗Считать, что
?.перехватывает исключение, а не сразу даётnull - ✗Ждать, что правая часть Elvis вычисляется жадно
- ✗Думать, что после обрыва звена остаток
a?.b?.cвсё же выполнится
Уточняющие вопросы
- →Как заставить
?:бросать исключение вместо возврата запасного значения? - →Какой тип у
a?.b, еслиbобъявлен как non-nullInt?
MiddleДебаггингЧастоПроверка на null проходит, но smart cast не компилируется — почему и как это исправить?
Проверка на null проходит, но smart cast не компилируется — почему и как это исправить?
Smart cast требует доказательства, что значение не изменится между проверкой и использованием. Изменяемое свойство может переприсвоить другой поток, поэтому в приведении отказано. Прочитайте его в локальную val; name?.length ?: 0 тоже годится.
Типичные ошибки
- ✗Думать, что smart cast работает на любом
var, включая свойство класса - ✗Хвататься за
!!вместо копирования значения в локальнуюval - ✗Считать, что свойство с пользовательским getter можно smart-cast-ить
Уточняющие вопросы
- →Почему свойство с пользовательским getter тоже не поддаётся smart cast?
- →Скомпилируется ли тот же код, будь
nameлокальнойvarвнутри функции?
MiddleТеорияЧастоЧем null-безопасность Kotlin отличается от идиомы-обёртки Optional в Java?
Чем null-безопасность Kotlin отличается от идиомы-обёртки Optional в Java?
Kotlin помещает отсутствие в сам тип — String?, — поэтому компилятор требует проверку при каждом использовании, и обёртка не выделяется. Optional — класс по желанию, для возвращаемых значений; поля и параметры не защищены, а ссылка на него может быть null.
Типичные ошибки
- ✗Думать, что
String?компилируется в обёрткуOptionalна JVM - ✗Забывать, что ссылка на сам
Optionalтоже может быть null - ✗Принимать
?за подсказку линтера, а не за тип, требуемый компилятором
Уточняющие вопросы
- →Почему
Optionalне советуют использовать для полей и параметров в Java? - →Какая идиома Kotlin заменяет цепочку
Optional.map(...).orElse(...)?
MiddleТеорияИногдаЧто такое тип Nothing в Kotlin и для чего он нужен на практике?
Что такое тип Nothing в Kotlin и для чего он нужен на практике?
Nothing — тип без единого значения: выражение такого типа никогда не завершается нормально. throw и TODO() имеют тип Nothing, поэтому x ?: throw E() проходит проверку типов. Это подтип любого типа, поэтому такой вызов подходит везде, где ждут значение.
Типичные ошибки
- ✗Путать
NothingсUnit, у которого значение как раз есть - ✗Называть
Nothingнадтипом, а не подтипом любого типа - ✗Упускать, что
throw— выражение, тип которогоNothing
Уточняющие вопросы
- →Какой тип у голого
nullи какNothing?это объясняет? - →Почему
emptyList()выводится какList<Nothing>и всё же подходит подList<String>?
SeniorТеорияИногдаГоворят, в Kotlin нет NPE — где это утверждение честно ломается?
Говорят, в Kotlin нет NPE — где это утверждение честно ломается?
Компилятор гарантирует лишь то, что может доказать. Nullability неаннотированной Java ему неизвестна, поэтому платформенный тип без проверки проскакивает в non-null тип — и NPE случается там; стабильность изменяемого свойства тоже недоказуема, и smart cast не сужает.
Типичные ошибки
- ✗Считать, что компилятор трактует неаннотированный тип Java как nullable
- ✗Полагать, что платформенный тип требует проверки на null перед использованием
- ✗Доверять non-null типу Java-getter без проверки на границе
Уточняющие вопросы
- →Как аннотации nullability на стороне Java меняют платформенный тип?
- →Почему NPE от платформенного типа часто всплывает далеко от вызова Java?