Какова структура иерархии типов nullable и not-nullable в Kotlin
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Иерархия типов Nullable и Not-Nullable в Kotlin
Котлин имеет уникальную систему типов, где каждый тип существует в двух вариантах: не-nullable (T) и nullable (T?). Это один из мощнейших инструментов для предотвращения NullPointerException.
Основная концепция
Kotlin делит все типы на две категории:
Any (супертип для всех)
├─ String (не-nullable)
├─ String? (nullable)
├─ Int (не-nullable)
├─ Int? (nullable)
├─ List<String> (не-nullable)
├─ List<String>? (nullable)
└─ ...
Not-Nullable типы (T)
Not-Nullable — тип, который гарантирует, что значение никогда не будет null.
val name: String = "Alice" // не может быть null
val age: Int = 25 // не может быть null
val list: List<String> = listOf("a", "b") // не может быть null
// ❌ Ошибка компиляции
val value: String = null // error: null cannot be a value of non-null type String
Преимущества:
- ✅ Безопасность — компилятор гарантирует, что значение существует
- ✅ Нет проверок — можно вызывать методы без проверки
- ✅ Производительность — нет runtime проверок
fun processString(text: String) {
println(text.length) // ✅ Безопасно, text не null
println(text.uppercase()) // ✅ Безопасно
}
Nullable типы (T?)
Nullable — тип, который может содержать значение или null.
val name: String? = "Bob" // может быть String или null
val age: Int? = null // может быть Int или null
val email: String? = "bob@example.com" // тоже может быть null
// ✅ Компиляция успешна
val value: String? = null // OK
Обработка Nullable:
fun processNullable(text: String?) {
// ❌ ОШИБКА! Не можем вызвать методы без проверки
// println(text.length)
// ✅ Способ 1: Safe call (?.) — возвращает null если text null
println(text?.length) // выведет null или число
// ✅ Способ 2: Elvis оператор (?: ) — значение по умолчанию
println(text?.length ?: 0) // если null, использует 0
// ✅ Способ 3: Not-null assertion (!!) — кидает исключение если null
println(text!!.length) // если null → NPE
// ✅ Способ 4: if-проверка
if (text != null) {
println(text.length) // here text is String, не String?
}
// ✅ Способ 5: when выражение
when (text) {
null -> println("No value")
else -> println(text.length)
}
}
Иерархия типов — диаграмма
Any?
/ \
/ \
Any null
/ | \
String Int List
/ | \
String? Int? List?
Важно: Any? — это супертип для всех типов, включая null.
val x: Any? = "text" // OK
val y: Any? = 42 // OK
val z: Any? = null // OK
// Но не наоборот!
val a: Any = null // ❌ Ошибка!
Наследование типов
Правило: String — подтип String?
val notNull: String = "Hello"
val nullable: String? = notNull // ✅ OK, String → String?
val x: String? = "World"
val y: String = x // ❌ Ошибка! String? → String
// Решение — smart cast после проверки
if (x != null) {
val y: String = x // ✅ OK, компилятор знает что x не null
}
Smart Casting (умная кастизация)
Компилятор Kotlin автоматически кастует nullable к not-null после проверки:
fun example(text: String?) {
if (text != null) {
// text автоматически стал String (не String?)
println(text.length) // ✅ OK, не нужен safe call
}
// Но за пределами блока if снова String?
// println(text.length) // ❌ Ошибка
}
Generic типы и Nullable
// Не-nullable List<String>
val list1: List<String> = listOf("a", "b") // элементы не null
// list1.add(null) // ❌ Ошибка
// Nullable List<String>? — сам список может быть null
val list2: List<String>? = null // OK, список null
val list3: List<String>? = listOf("a", "b") // OK, список существует
// List<String?> — элементы списка могут быть null
val list4: List<String?> = listOf("a", null, "b") // элементы могут быть null
val list5: List<String?> = listOf("a", "b") // тоже OK
// List<String?>? — всё может быть null
val list6: List<String?>? = null // ✅ OK
val list7: List<String?>? = listOf("a", null) // ✅ OK
Практические примеры
Пример 1: Функции с nullable параметрами
fun getUserName(user: User?): String {
// Вариант 1: Elvis оператор
return user?.name ?: "Unknown"
// Вариант 2: safe call + let
return user?.let { it.name } ?: "Unknown"
// Вариант 3: if проверка
return if (user != null) user.name else "Unknown"
}
getUserName(null) // вернёт "Unknown"
getUserName(User("Alice")) // вернёт "Alice"
Пример 2: Chain операций с nullable
data class Company(val department: Department?)
data class Department(val manager: Employee?)
data class Employee(val name: String)
val company = Company(null)
// Safe call chain
val managerName: String? = company.department?.manager?.name
println(managerName) // null
// С Elvis оператором
val name = company.department?.manager?.name ?: "No manager"
println(name) // "No manager"
Пример 3: Конвертация типов
fun getString(): String? = null
// Проверка и использование
val result = getString()
if (result != null) {
println(result.length) // String, не String?
}
// Или let блок
getString()?.let { text ->
println(text.length) // text: String
}
Типичные ошибки
// ❌ Ошибка 1: забыли ? после типа
val x: String = null // error!
// ✅ Правильно
val x: String? = null // OK
// ❌ Ошибка 2: вызов метода на nullable без проверки
val text: String? = "hello"
println(text.length) // error!
// ✅ Правильно
println(text?.length) // OK
// ❌ Ошибка 3: опасный !! оператор
val value: String? = null
println(value!!.length) // NPE в runtime!
// ✅ Правильно
println(value?.length ?: 0) // OK
Вывод
Иерархия типов в Kotlin:
Верхушка: Any? (может содержать что угодно или null)
Уровень 1: Any (не может быть null)
Уровень 2: Конкретные типы
String— строка, не nullString?— строка или nullList<T>— список, не nullList<T>?— список или null
Ключевые моменты:
T≠T?(это разные типы)Tэто подтипT?- Compile-time safety благодаря системе типов
- Smart casting после проверок null
- Используй safe calls
?.вместо!!