11 KiB
title, description, tags
| title | description | tags | ||
|---|---|---|---|---|
| Any, Unit, Nothing | Корень иерархии Any, тип-единица Unit и нижний тип Nothing |
|
Три особых типа образуют каркас системы типов Kotlin.
| Тип | Сколько значений | Роль |
|---|---|---|
Any |
все объекты | супертип всего |
Unit |
одно (Unit) |
«результата нет» |
Nothing |
ни одного | «возврата не будет» |
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 типов. Объявляет три метода:
fun main() {
val x: Any = 42
println(x.toString()) // 42
println(x.hashCode()) // 42
println(x == 42) // true — вызов equals
}
Больше у Any ничего нет: ни wait, ни notify, ни getClass.
fun main() {
val items: List<Any> = listOf(1, "два", 3.0, true)
println(items.joinToString { it::class.simpleName ?: "?" })
// Int, String, Double, Boolean
}
Any не принимает null - для этого есть Any?, самый широкий тип в языке:
fun main() {
// val a: Any = null // ошибка компиляции
val b: Any? = null // ok
println(b) // null
}
Расширения Any? можно звать на null:
fun main() {
val x: String? = null
println(x.toString()) // "null" — расширение Any?.toString()
println(x.hashCode()) // 0
}
Это не методы Any, а функции-расширения stdlib, объявленные для Any?.
Unit
Тип функций, которые ничего не возвращают. Это настоящий объект, а не отсутствие значения:
fun log(msg: String): Unit = println(msg)
fun main() {
val result = log("привет")
println(result) // kotlin.Unit
println(result == Unit) // true
}
Unit - object, у него ровно одно значение. Тип результата Unit можно опускать, и обычно так и делают:
fun log(msg: String) = println(msg) // тип выведен: Unit
fun log2(msg: String) { println(msg) } // то же самое
Поскольку Unit - полноценный тип, он подставляется в дженерики, где void в Java не работает:
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 последнее выражение игнорируется - возвращать ничего не нужно:
fun run2(block: () -> Unit) = block()
fun main() {
run2 { 42 } // 42 отбрасывается, лямбда подходит под () -> Unit
println("ok")
}
Nothing
Тип без значений. Функция с результатом Nothing не может завершиться нормально - только исключением или бесконечным циклом:
fun fail(message: String): Nothing = throw IllegalStateException(message)
fun main() {
val x: String = if (true) "ok" else fail("нет значения")
println(x) // ok
}
Зачем это нужно: Nothing - подтип любого типа, поэтому выражение с типом Nothing подходит в любую позицию. Компилятор знает, что дальше кода не будет.
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:
fun main() {
val x: Int = try {
"abc".toInt()
} catch (e: NumberFormatException) {
throw IllegalStateException("плохие данные", e) // Nothing подходит
}
println(x)
}
Анализ недостижимого кода
fun fail(): Nothing = throw RuntimeException()
fun main() {
val x = 1
if (x > 0) {
println("да")
} else {
fail()
// println("недостижимо") // предупреждение компилятора
}
}
Компилятор использует Nothing, чтобы доказать: ветка не продолжается. Поэтому TODO() работает где угодно:
fun calculate(x: Int): String = TODO("ещё не реализовано")
fun main() {
// calculate(1) // NotImplementedError
println("компилируется")
}
TODO() объявлена как fun TODO(): Nothing, поэтому подходит под любой тип результата.
Nothing? и null
fun main() {
val x = null
println(x) // null
// тип x — Nothing?, единственное значение которого null
val list = listOf(null, null) // List<Nothing?>
println(list.size) // 2
}
Пустые коллекции тоже используют Nothing:
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 ObjectAny(но безwait/notify/getClass)voidUnitVoidUnit(илиNothing?)нет аналога NothingObject 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-компилятор не знает, что метод никогда не вернёт управление:int parse(String s) { if (s == null) fail("null"); // fail объявлен void return Integer.parseInt(s); // компилятор требует этот return }В Kotlin
fail(): Nothingсообщает компилятору, что ветка обрывается, и анализ потока это учитывает.Сравнить: Класс Object в Java
[!warning] Подводные камни
Any- не то же самое, чтоAny?. Функция, принимающаяAny, не приметnull:fun show(x: Any) = x.toString() fun main() { val n: String? = null // println(show(n)) // ошибка компиляции println(show(n ?: "пусто")) // пусто }Лямбда с результатом
Unitмолча глотает значение:fun main() { val items = mutableListOf<Int>() listOf(1, 2).forEach { items.add(it) } // add возвращает Boolean, println(items) // но тип () -> Unit его отбрасывает }Это обычно удобно, но если по ошибке вместо
mapнаписатьforEach, результат исчезнет без предупреждения.
returnс типомUnitписать не нужно, аreturn Unit- признак непонимания:fun f(): Unit { return Unit // компилируется, но так не пишут }Функция с типом
Nothingне может иметь тело-блок сreturn:fun fail(): Nothing { // return // ошибка: ничего нельзя вернуть throw RuntimeException() // только выход через исключение }
val x = nullимеет типNothing?, а неAny?. Присвоить такой переменной что-либо другое нельзя:fun main() { var x = null // x = "строка" // ошибка: ожидался Nothing? val y: String? = null // так println("$x $y") }
Nothingв дженерике означает «значений не будет»:fun main() { val producer: () -> Nothing = { throw RuntimeException("всегда падает") } // producer() — вызов гарантированно бросит исключение println("тип ${producer::class.simpleName} объявлен, но вызов невозможен") }Если такой тип получился случайно (например,
emptyList()без указания параметра), в коллекцию ничего нельзя добавить.
Дополнительно: equals, hashCode и toString Далее: Data class