Added kotlin guide. (WIP) (15.10.26)

This commit is contained in:
2026-10-15 00:24:21 +03:00
parent dc82207723
commit da3ad9dd1a
13 changed files with 2120 additions and 0 deletions
@@ -0,0 +1,185 @@
---
title: Null-безопасность
description: Разделение типов на nullable и non-null, как компилятор предотвращает NPE
tags:
- kotlin
- null-safety
---
Главная особенность системы типов Kotlin: **возможность быть null - часть типа**.
```kotlin
var a: String = "text"
// a = null // ошибка компиляции
var b: String? = "text"
b = null // ok
```
`String` и `String?` - разные типы. `String` является подтипом `String?`, но не наоборот.
```mermaid
graph TD
SN["String? — допускает null"]
S["String — не допускает null"]
N["Nothing? — только null"]
SN --> S
SN --> N
```
## Зачем это нужно
`NullPointerException` в Java возникает в рантайме, иногда далеко от места, где null попал в переменную. В Kotlin компилятор отказывается собирать такой код:
```kotlin
fun length(s: String?): Int {
// return s.length // ошибка: only safe (?.) or non-null (!!) calls are allowed
return s?.length ?: 0
}
```
Проблема переносится с этапа выполнения на этап компиляции. Разыменовать nullable-значение без проверки невозможно.
## Работа с nullable
Пять подходов, от предпочтительного к крайнему:
### 1. Проверка на null
После проверки компилятор сам приводит тип:
```kotlin
fun describe(s: String?): String {
if (s == null) return "нет значения"
return "длина ${s.length}" // здесь s уже String, не String?
}
```
Это [умное приведение](Умные%20приведения.md).
### 2. Безопасный вызов - ?.
```kotlin
val len: Int? = s?.length // null, если s == null
```
### 3. Elvis - ?:
```kotlin
val len: Int = s?.length ?: 0 // значение по умолчанию
val name = user?.name ?: error("нет пользователя") // или выход
```
### 4. Безопасное приведение - as?
```kotlin
val n: Int? = value as? Int // null вместо ClassCastException
```
### 5. Утверждение - !!
```kotlin
val len: Int = s!!.length // NPE, если s == null
```
`!!` означает «я гарантирую, что здесь не null». Каждое его использование - это место, где вы отключили проверку компилятора.
Подробнее про операторы: [Операторы null-безопасности](Операторы%20null-безопасности.md)
## Nullable в коллекциях
`?` ставится раздельно для элементов и для самой коллекции:
```kotlin
val a: List<String> = listOf("x") // список есть, элементы не null
val b: List<String?> = listOf(null) // список есть, элементы могут быть null
val c: List<String>? = null // списка может не быть
val d: List<String?>? = null // и то, и другое
```
Для отсеивания null есть готовые функции:
```kotlin
val items: List<String?> = listOf("a", null, "b")
println(items.filterNotNull()) // [a, b]
```
## Nullable и дженерики
Параметр типа по умолчанию допускает null, его верхняя граница - `Any?`:
```kotlin
fun <T> process(item: T) { } // T может быть nullable
fun <T : Any> strict(item: T) { } // T гарантированно non-null
strict("text")
// strict(null) // ошибка компиляции
```
Подробнее: [Параметрический полиморфизм](../../Дженерики/Параметрический%20полиморфизм.md)
## Где NPE всё-таки возможен
Kotlin не даёт абсолютной гарантии. Источники NPE:
1. **Явный `!!`** - вы сами попросили
2. **Явный `throw NullPointerException()`**
3. **Код на Java** - типы оттуда компилятор проверить не может ([Платформенные типы](Платформенные%20типы.md))
4. **`lateinit` до инициализации** - технически `UninitializedPropertyAccessException`
5. **Утечка `this` из конструктора** - обращение к ещё не инициализированному свойству
```kotlin
class Broken {
val a: String = compute() // вызывается до инициализации b
val b: String = "value"
fun compute(): String = b.uppercase() // NPE: b ещё null
}
```
Последний случай компилятор обычно замечает и выдаёт предупреждение, но через несколько уровней вызовов - может и пропустить.
## Правило
Если у вас много `!!` - типы спроектированы неправильно. Nullable-тип должен появляться там, где отсутствие значения - реальная часть предметной области («у пользователя может не быть отчества»), а не там, где лень разбираться с инициализацией.
> [!info] Отличия от Java
> | Java | Kotlin |
> |---|---|
> | любая ссылка может быть null | null допускают только типы с `?` |
> | NPE в рантайме | ошибка компиляции |
> | `Optional<T>` как обёртка | `T?` на уровне типов, без аллокации |
> | `@Nullable`/`@NotNull` - подсказки для IDE | часть системы типов, проверяется компилятором |
> | `Objects.requireNonNull(x)` | `requireNotNull(x)` или просто non-null тип |
>
> `Optional<T>` в Java - это объект-обёртка: аллокация, нет поддержки в полях, не композируется с дженериками. `T?` в Kotlin не создаёт обёртки: в байт-коде это обычная ссылка, а проверки расставляет компилятор.
>
> Публичные функции Kotlin получают проверки параметров автоматически - вызов из Java с null упадёт сразу на входе, с понятным сообщением, а не глубже по стеку:
> ```kotlin
> fun greet(name: String) = "Hi, $name"
> ```
> ```java
> GreetKt.greet(null);
> // NullPointerException: Parameter specified as non-null is null:
> // method GreetKt.greet, parameter name
> ```
> В сообщении указаны и метод, и имя параметра.
>
> Сравнить: [Приведение ссылочных типов в Java](../../../Программирование%20на%20Java/Синтаксис%20и%20типы/Работа%20с%20данными%20и%20типами/Приведение%20ссылочных%20типов.md)
> [!warning] Подводные камни
> **`!!` - не решение, а отложенная проблема.** Строка `user!!.profile!!.name!!` падает без указания, какое из трёх звеньев было null.
>
> **Умное приведение не работает с `var` из другого потока и с открытыми свойствами:**
> ```kotlin
> class Holder {
> var value: String? = null
> fun use() {
> if (value != null) {
> // println(value.length) // ошибка: smart cast невозможен
> println(value!!.length) // или скопировать в локальную val
> }
> }
> }
> ```
> Подробнее: [Умные приведения](Умные%20приведения.md)
>
> **Платформенные типы из Java проверок не имеют:**
> ```kotlin
> val s: String = javaMethodThatReturnsNull() // скомпилируется, упадёт в рантайме
> ```
>
> **`Boolean?` нельзя использовать в `if` напрямую:**
> ```kotlin
> val flag: Boolean? = null
> // if (flag) { } // ошибка компиляции
> if (flag == true) { } // так
> if (flag ?: false) { } // или так
> ```
>
> **`null` в строковом шаблоне превращается в текст:**
> ```kotlin
> val x: String? = null
> println("значение: $x") // значение: null
> ```
Далее: [Операторы null-безопасности](Операторы%20null-безопасности.md)