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,160 @@
---
title: Платформенные типы
description: Тип T! из Java-кода, где null-безопасность Kotlin перестаёт работать и как это чинить
tags:
- kotlin
- null-safety
- interop
---
Платформенный тип (platform type) - тип, пришедший из Java, про nullability которого компилятор Kotlin ничего не знает. Обозначается `T!`, но написать его в коде нельзя - он существует только в сообщениях компилятора и в IDE.
```java
// Java
public class Repo {
public String findName() { return null; }
}
```
```kotlin
// Kotlin
val repo = Repo()
val a: String = repo.findName() // компилируется! падает в рантайме
val b: String? = repo.findName() // тоже компилируется
```
`findName()` имеет тип `String!` - его можно присвоить и в `String`, и в `String?`. Проверку компилятор не делает.
## Почему так
Альтернативы были хуже:
- считать все Java-типы nullable → весь Java-код пришлось бы обвешивать `!!` и `?.`, включая `System.out`
- считать все non-null → ложное чувство безопасности без возможности это заметить
Платформенный тип - компромисс: ответственность за nullability возвращается программисту, как в Java.
## Где падает
NPE возникает в точке присваивания, а не использования - компилятор вставляет проверку на границе:
```kotlin
val a: String = repo.findName() // NPE здесь же
println(a.length) // до этой строки не дойдёт
```
Это лучше, чем в Java: падение происходит рядом с источником, а не там, куда значение утекло.
## Как чинить
### 1. Указывать nullable-тип явно
Любое значение из Java трактуйте как `T?`, пока не убедились в обратном:
```kotlin
val name: String? = repo.findName()
println(name?.length ?: 0)
```
### 2. Аннотации в Java-коде
Компилятор Kotlin читает аннотации nullability и начинает проверять типы:
```java
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.NotNull;
public class Repo {
@Nullable public String findName() { return null; }
@NotNull public String getId() { return "1"; }
}
```
```kotlin
val a: String = repo.findName() // теперь ошибка компиляции
val b: String? = repo.findName() // ok
val c: String = repo.getId() // ok, проверка не нужна
```
Поддерживаются аннотации из нескольких наборов:
| Набор | Пакет |
|---|---|
| JetBrains | `org.jetbrains.annotations` |
| JSpecify | `org.jspecify.annotations` |
| Android | `androidx.annotation` |
| JSR-305 | `javax.annotation` |
| Eclipse | `org.eclipse.jdt.annotation` |
| Lombok | `lombok.NonNull` |
### 3. Обёртка на границе
Для внешней библиотеки без аннотаций - свой слой, который один раз разбирается с nullability:
```kotlin
// вся работа с «грязными» типами собрана здесь
fun Repo.nameOrNull(): String? = findName()
fun Repo.requireName(): String = findName() ?: error("name отсутствует")
```
Дальше код работает с честными типами Kotlin.
### 4. Строгий режим компилятора
Флаг `-Xjsr305=strict` заставляет компилятор относиться к JSR-305-аннотациям как к обязательным:
```kotlin
// build.gradle.kts
kotlin {
compilerOptions {
freeCompilerArgs.add("-Xjsr305=strict")
}
}
```
Значения: `ignore`, `warn` (по умолчанию), `strict`.
## Коллекции из Java
Платформенность распространяется на параметры типа:
```java
public List<String> getItems() { return null; }
```
Тип результата - `(Mutable)List<String!>!`. Неизвестно ни то, может ли быть null сам список, ни его элементы:
```kotlin
val items: List<String?>? = repo.items // самое честное предположение
items?.filterNotNull()?.forEach(::println)
```
Отдельная проблема - изменяемость. `java.util.List` отображается в `(Mutable)List`, то есть Kotlin позволит передать его туда, где ожидается `MutableList`, и Java-код может изменить «неизменяемый» список:
```kotlin
val list: List<String> = repo.items // на самом деле ArrayList
// list.add("x") // так нельзя, тип List
(list as MutableList).add("x") // а так — да, и это сработает
```
Подробнее: [Java из Kotlin](../../Взаимодействие%20с%20Java/Java%20из%20Kotlin.md)
## Обратное направление
Код на Kotlin для Java-вызывающего выглядит аннотированным: компилятор добавляет `@NotNull`/`@Nullable` в байт-код автоматически. Плюс в публичные функции вставляются проверки параметров:
```kotlin
fun greet(name: String) = "Hi, $name"
```
```java
GreetKt.greet(null);
// NullPointerException: Parameter specified as non-null is null:
// method GreetKt.greet, parameter name
```
Падение происходит на входе в функцию, с указанием метода и имени параметра.
> [!info] Отличия от Java
> В Java платформенных типов нет - там все ссылки одинаково могут быть null, и никакой проверки не существует.
>
> Платформенные типы - это механизм совместимости, а не фича языка. Их задача - позволить Kotlin вызывать существующие библиотеки без переписывания. Чем больше кода в проекте на Kotlin, тем реже они встречаются.
>
> Сравнить: [Система исключений в Java](../../../Программирование%20на%20Java/Исключения/Система%20исключений.md)
> [!warning] Подводные камни
> **Самый опасный случай - значение «просачивается» в non-null тип:**
> ```kotlin
> data class User(val name: String)
> val user = User(repo.findName()) // NPE в конструкторе, а причина — в Java-методе
> ```
>
> **IDE подсказывает тип `String!`, но в коде так написать нельзя.** Если увидели `!` в подсказке - значит, компилятор вас не защищает.
>
> **Аннотации на уровне пакета легко пропустить.** `@NonNullApi` или `package-info.java` с `@ParametersAreNonnullByDefault` меняют поведение для всего пакета. Spring, например, аннотирует свои пакеты - поэтому его API в Kotlin выглядит типизированным.
>
> **`-Xjsr305=strict` может сломать сборку при обновлении библиотеки** - новые аннотации превратятся в ошибки компиляции. Включать осознанно.
>
> **Дженерик-методы Java особенно коварны:**
> ```java
> public <T> T getOrNull(String key) { return null; }
> ```
> ```kotlin
> val x: String = repo.getOrNull("k") // T выведется в String, NPE в рантайме
> ```
>
> **Массивы из Java тоже платформенные:** `String[]` → `Array<(out) String!>!`. Элементы могут быть null даже если сам массив не null.
Дополнительно: [Java из Kotlin](../../Взаимодействие%20с%20Java/Java%20из%20Kotlin.md)
Далее: [Строки](../Работа%20с%20данными/Строки.md)