Kotlin guide done. (20.10.26)
This commit is contained in:
@@ -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)
|
||||
Reference in New Issue
Block a user