Kotlin guide done. (20.10.26)

This commit is contained in:
2026-10-20 12:54:25 +03:00
parent da3ad9dd1a
commit 6853ada348
81 changed files with 30112 additions and 0 deletions
@@ -0,0 +1,274 @@
---
title: Any, Unit, Nothing
description: Корень иерархии Any, тип-единица Unit и нижний тип Nothing
tags:
- kotlin
- types
---
Три особых типа образуют каркас системы типов Kotlin.
| Тип | Сколько значений | Роль |
|---|---|---|
| `Any` | все объекты | супертип всего |
| `Unit` | одно (`Unit`) | «результата нет» |
| `Nothing` | ни одного | «возврата не будет» |
```mermaid
graph TD
A["Any?"] --> B["Any"]
A --> N1["Nothing?"]
B --> C["String"]
B --> D["Int"]
B --> E["Unit"]
C --> N["Nothing"]
D --> N
E --> N
N1 --> N
```
`Any` сверху, `Nothing` снизу: он подтип любого типа.
## Any
Супертип всех non-null типов. Объявляет три метода:
```kotlin
fun main() {
val x: Any = 42
println(x.toString()) // 42
println(x.hashCode()) // 42
println(x == 42) // true — вызов equals
}
```
Больше у `Any` ничего нет: ни `wait`, ни `notify`, ни `getClass`.
```kotlin
fun main() {
val items: List<Any> = listOf(1, "два", 3.0, true)
println(items.joinToString { it::class.simpleName ?: "?" })
// Int, String, Double, Boolean
}
```
`Any` не принимает `null` - для этого есть `Any?`, самый широкий тип в языке:
```kotlin
fun main() {
// val a: Any = null // ошибка компиляции
val b: Any? = null // ok
println(b) // null
}
```
Расширения `Any?` можно звать на `null`:
```kotlin
fun main() {
val x: String? = null
println(x.toString()) // "null" — расширение Any?.toString()
println(x.hashCode()) // 0
}
```
Это не методы `Any`, а функции-расширения stdlib, объявленные для `Any?`.
## Unit
Тип функций, которые ничего не возвращают. Это **настоящий объект**, а не отсутствие значения:
```kotlin
fun log(msg: String): Unit = println(msg)
fun main() {
val result = log("привет")
println(result) // kotlin.Unit
println(result == Unit) // true
}
```
`Unit` - `object`, у него ровно одно значение. Тип результата `Unit` можно опускать, и обычно так и делают:
```kotlin
fun log(msg: String) = println(msg) // тип выведен: Unit
fun log2(msg: String) { println(msg) } // то же самое
```
Поскольку `Unit` - полноценный тип, он подставляется в дженерики, где `void` в Java не работает:
```kotlin
fun main() {
val actions: List<() -> Unit> = listOf({ print("a") }, { print("b") })
actions.forEach { it() } // ab
println()
val map = mutableMapOf<String, Unit>()
map["ключ"] = Unit // Unit как значение — законно
println(map) // {ключ=kotlin.Unit}
}
```
В лямбде с типом результата `Unit` последнее выражение игнорируется - возвращать ничего не нужно:
```kotlin
fun run2(block: () -> Unit) = block()
fun main() {
run2 { 42 } // 42 отбрасывается, лямбда подходит под () -> Unit
println("ok")
}
```
## Nothing
Тип без значений. Функция с результатом `Nothing` **не может завершиться нормально** - только исключением или бесконечным циклом:
```kotlin
fun fail(message: String): Nothing = throw IllegalStateException(message)
fun main() {
val x: String = if (true) "ok" else fail("нет значения")
println(x) // ok
}
```
Зачем это нужно: `Nothing` - подтип любого типа, поэтому выражение с типом `Nothing` подходит в любую позицию. Компилятор знает, что дальше кода не будет.
```kotlin
fun fail(msg: String): Nothing = throw IllegalArgumentException(msg)
fun parse(s: String): Int = s.toIntOrNull() ?: fail("не число: $s")
fun main() {
println(parse("42")) // 42
// parse("abc") // IllegalArgumentException: не число: abc
}
```
Если бы `fail` возвращала `Unit`, этот код не скомпилировался бы: `Int` и `Unit` несовместимы.
`throw` сам по себе - выражение типа `Nothing`:
```kotlin
fun main() {
val x: Int = try {
"abc".toInt()
} catch (e: NumberFormatException) {
throw IllegalStateException("плохие данные", e) // Nothing подходит
}
println(x)
}
```
### Анализ недостижимого кода
```kotlin
fun fail(): Nothing = throw RuntimeException()
fun main() {
val x = 1
if (x > 0) {
println("да")
} else {
fail()
// println("недостижимо") // предупреждение компилятора
}
}
```
Компилятор использует `Nothing`, чтобы доказать: ветка не продолжается. Поэтому `TODO()` работает где угодно:
```kotlin
fun calculate(x: Int): String = TODO("ещё не реализовано")
fun main() {
// calculate(1) // NotImplementedError
println("компилируется")
}
```
`TODO()` объявлена как `fun TODO(): Nothing`, поэтому подходит под любой тип результата.
### Nothing? и null
```kotlin
fun main() {
val x = null
println(x) // null
// тип x — Nothing?, единственное значение которого null
val list = listOf(null, null) // List<Nothing?>
println(list.size) // 2
}
```
Пустые коллекции тоже используют `Nothing`:
```kotlin
fun main() {
val empty: List<Nothing> = emptyList()
val strings: List<String> = empty // подходит — List ковариантен
println(strings.size) // 0
}
```
Один объект `EmptyList` подходит под `List<T>` для любого `T` именно потому, что `List<Nothing>` - подтип всех `List<T>`.
> [!info] Отличия от Java
> | Java | Kotlin |
> |---|---|
> | `Object` | `Any` (но без `wait`/`notify`/`getClass`) |
> | `void` | `Unit` |
> | `Void` | `Unit` (или `Nothing?`) |
> | нет аналога | `Nothing` |
> | `Object o = null;` | `Any? = null`, `Any` не принимает null |
>
> **`Any` беднее `Object`.** Методы `wait()`, `notify()`, `getClass()` в `Any` не объявлены: первые два относятся к устаревшей модели синхронизации, третий заменён на `::class`. На JVM они доступны после приведения к `Object`, но из Kotlin их намеренно не видно.
>
> **`Unit` против `void`** - не косметика. `void` не тип, поэтому в Java нельзя написать `List<void>` или `Function<String, void>`; приходится использовать `Void` (у которого единственное значение - `null`) или `Consumer`. В Kotlin `() -> Unit` - обычный тип.
>
> **`Nothing` в Java нет вообще.** Из-за этого Java-компилятор не знает, что метод никогда не вернёт управление:
> ```java
> int parse(String s) {
> if (s == null) fail("null"); // fail объявлен void
> return Integer.parseInt(s); // компилятор требует этот return
> }
> ```
> В Kotlin `fail(): Nothing` сообщает компилятору, что ветка обрывается, и анализ потока это учитывает.
>
> Сравнить: [Класс Object в Java](../../../Программирование%20на%20Java/Синтаксис%20и%20типы/Базовые%20типы%20ООП/Класс%20Object.md)
> [!warning] Подводные камни
> **`Any` - не то же самое, что `Any?`.** Функция, принимающая `Any`, не примет `null`:
> ```kotlin
> fun show(x: Any) = x.toString()
>
> fun main() {
> val n: String? = null
> // println(show(n)) // ошибка компиляции
> println(show(n ?: "пусто")) // пусто
> }
> ```
>
> **Лямбда с результатом `Unit` молча глотает значение:**
> ```kotlin
> fun main() {
> val items = mutableListOf<Int>()
> listOf(1, 2).forEach { items.add(it) } // add возвращает Boolean,
> println(items) // но тип () -> Unit его отбрасывает
> }
> ```
> Это обычно удобно, но если по ошибке вместо `map` написать `forEach`, результат исчезнет без предупреждения.
>
> **`return` с типом `Unit` писать не нужно**, а `return Unit` - признак непонимания:
> ```kotlin
> fun f(): Unit {
> return Unit // компилируется, но так не пишут
> }
> ```
>
> **Функция с типом `Nothing` не может иметь тело-блок с `return`:**
> ```kotlin
> fun fail(): Nothing {
> // return // ошибка: ничего нельзя вернуть
> throw RuntimeException() // только выход через исключение
> }
> ```
>
> **`val x = null` имеет тип `Nothing?`, а не `Any?`.** Присвоить такой переменной что-либо другое нельзя:
> ```kotlin
> fun main() {
> var x = null
> // x = "строка" // ошибка: ожидался Nothing?
> val y: String? = null // так
> println("$x $y")
> }
> ```
>
> **`Nothing` в дженерике означает «значений не будет»:**
> ```kotlin
> fun main() {
> val producer: () -> Nothing = { throw RuntimeException("всегда падает") }
> // producer() — вызов гарантированно бросит исключение
> println("тип ${producer::class.simpleName} объявлен, но вызов невозможен")
> }
> ```
> Если такой тип получился случайно (например, `emptyList()` без указания параметра), в коллекцию ничего нельзя добавить.
Дополнительно: [equals, hashCode и toString](equals,%20hashCode%20и%20toString.md)
Далее: [Data class](Data%20class.md)