Files
obsidian/Kotlin/Синтаксис и типы/Базовые типы ООП/Any, Unit, Nothing.md
T

275 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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)