← Назад к вопросам

Какова структура иерархии типов nullable и not-nullable в Kotlin

2.2 Middle🔥 111 комментариев
#Kotlin основы

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Иерархия типов 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 — строка, не null
  • String? — строка или null
  • List<T> — список, не null
  • List<T>? — список или null

Ключевые моменты:

  • TT? (это разные типы)
  • T это подтип T?
  • Compile-time safety благодаря системе типов
  • Smart casting после проверок null
  • Используй safe calls ?. вместо !!