Навчальний простір / Kotlin
Програмування мобільних застосунків

Kotlin для Android: типи, null safety та моделі даних

Kotlin типи, null safety та функції роблять контракти видимими.

Зміст матеріалу

Що ви опануєте

Ви навчитеся створювати Kotlin моделі, перевіряти введення, працювати з колекціями та передавати поведінку через lambda. Ці знання продовжують першу лекцію про Android і готують до Compose екранів. Наскрізний приклад — перелік навчальних завдань із позначенням виконання. Кожне правило спочатку пояснимо звичайною функцією без залежності від UI.

Типи та оголошення

val задає посилання або значення, якому не можна звичайно присвоїти інший результат; var дозволяє переприсвоєння. Для посилального об’єкта val не означає глибоку незмінність його вмісту. Статичний тип перевіряється compiler. Int містить ціле число, Double — наближене дробове, Boolean — логічне, String — текст.

val count = 3 виводить тип Int. Явний тип корисний на межі API й там, де потрібно пояснити контракт. Вираз 5 / 2 з Int дає 2; для дробового результату потрібно 5.0 / 2. Переповнення Int у JVM не є правильною реалізацією предметного діапазону, тому перед обчисленням великих значень перевіряйте межі або використовуйте відповідний тип.

Nullable та перевірка введення

String? може містити текст або null. Safe call ?. виконує доступ лише за наявності значення. Elvis ?: задає результат для null. !! стверджує ненульовість і спричиняє помилку, якщо припущення хибне. Введення користувача є зовнішніми даними, тому його потрібно перевіряти, а не довільно примушувати до потрібного типу.

kotlin
fun parseMinutes(raw: String): Int? {
    val value = raw.trim().toIntOrNull() ?: return null
    return value.takeIf { it in 1..240 }
}

fun main() {
    println(parseMinutes(" 30 ")) // 30
    println(parseMinutes("abc"))  // null
    println(parseMinutes("0"))    // null
}

trim прибирає крайні пробіли. toIntOrNull відхиляє нечисловий текст і значення поза діапазоном Int. Раннє return завершує функцію. takeIf залишає значення, якщо lambda повертає true; it є її параметром. Діапазон 1..240 включає обидві границі. Null тут означає непридатне введення за погодженим контрактом.

Функції та вирази

Функція має параметри й тип результату. Kotlin дозволяє expression body, коли результат визначає один вираз. if і when можуть повертати значення. Параметри за замовчуванням роблять виклик коротшим, але повинні відповідати очікуваній поведінці. Іменовані аргументи допомагають читати кілька однотипних параметрів.

kotlin
fun progressLabel(done: Int, total: Int): String {
    require(total >= 0 && done in 0..total) { "Некоректний прогрес" }
    return when {
        total == 0 -> "Завдань ще немає"
        done == total -> "Усе виконано"
        else -> "Виконано $done із $total"
    }
}

require перевіряє контракт аргументів і кидає IllegalArgumentException. Перший branch обробляє порожній набір до порівняння завершення. Інтерполяція підставляє значення в рядок. Зовнішній UI повинен мати політику для помилок введення; require підходить для порушення контракту функції, а не для кожного натискання користувача.

Data class та копія стану

Data class автоматично надає корисні операції для даних, зокрема equals, toString і copy на основі властивостей primary constructor. Властивості val роблять переприсвоєння забороненим, але вкладені змінювані об’єкти все ще потребують власної дисципліни. Copy є поверхневим для таких посилань.

kotlin
data class StudyTask(val id: Int, val title: String, val completed: Boolean = false)
val original = StudyTask(1, "Kotlin")
val updated = original.copy(completed = true)

Original залишається completed=false, updated має true. Для Compose така заміна простого immutable стану робить зміни видимими через відповідний observable holder. Data class сам по собі не запускає recomposition; потрібно змінити стан, який UI спостерігає.

Колекції та lambda

List<T> надає інтерфейс читання впорядкованих елементів, MutableList<T> — зміни. Read-only інтерфейс не гарантує глибоку незмінність джерела. map перетворює кожен елемент, filter відбирає, count рахує за умовою. Lambda описує передану поведінку й може захоплювати значення зовнішньої області.

kotlin
val tasks = listOf(StudyTask(1, "Kotlin"), StudyTask(2, "Compose", true))
val completedTitles = tasks.filter { it.completed }.map { it.title }
val next = tasks.map { if (it.id == 1) it.copy(completed = true) else it }

Фрагмент створює нові колекції й не змінює початковий List. ID пов’язує подію з предметним елементом. Пошук за позицією може стати неправильним після сортування або фільтрації, тому стабільна ідентичність важлива для UI списків.

Типові помилки та практика

Помилки: !! для неперевіреного введення, Int ділення для частки, плутанина val із незмінністю всього об’єкта й зміна MutableList без observable повідомлення. Налагоджуйте чисту функцію в Kotlin scratch або модульному тесті до включення в Android screen. Запишіть звичайні, граничні й помилкові значення.

Практичний аналіз

Для списку завдань визначте правило перейменування й перевірте порожню назву. Поясніть, як map створить новий стан за ID. Який тип результату парсингу потрібний, якщо UI має розрізняти нечисловий текст і число поза діапазоном? Таке розширення повинно додати явні причини відмови.

Як Kotlin допомагає описувати допустимі дані

Тип String? дозволяє відсутність тексту, а String очікує наявне значення. Це впливає на дозволені операції: прямий доступ до nullable-значення потребує перевірки або оператора безпечного виклику. Elvis-оператор ?: задає результат, коли ліва частина null. Підставляйте значення лише тоді, коли воно має предметний зміст, бо випадковий нуль може приховати помилку введення.

ToIntOrNull повертає null, якщо текст не представляє допустиме int. Після успішного перетворення все одно потрібна перевірка діапазону: число −5 є коректним int, але може бути некоректною тривалістю. Технічна й предметна валідність є послідовними кроками обробки. Повідомлення користувачу повинне пояснювати, який крок не пройдено.

Data class генерує операції для значущих властивостей основного конструктора. Copy створює новий екземпляр із заданими змінами, але вкладені змінювані колекції можуть залишатися спільними. Якщо стан інтерфейсу містить List, використовуйте узгоджену політику незмінності та створення нової колекції. Val забороняє переприсвоєння змінної, але не обов’язково зміну об’єкта, на який вона посилається.

Лямбда описує передану поведінку. Callback onSubmit передає подію від UI до власника логіки; параметри повинні представляти достатні дані для рішення. Якщо callback отримує вже перевірену тривалість, поясніть, де гарантується ця передумова. Якщо отримує сирий текст, логіка повинна виконати перетворення й повернути стан помилки. Так сигнатура допомагає читати межі відповідальності.

Схема процесу

Kotlin для Android: типи, null safety та моделі данихОберіть елемент, щоб побачити пояснення

Перевіряємо зовнішній текст.

Самоперевірка

Перевірте розуміння
Чи робить val MutableList незмінюваною?
Як обґрунтувати відповідь своїми словами?
Val забороняє переприсвоєння змінної. Можливість змінювати об’єкт визначає його тип і доступний інтерфейс.

Підсумок

Kotlin типи, null safety та функції роблять контракти видимими. Data class і колекційні операції допомагають представляти новий стан без прихованих змін. Observable UI потребує окремого повідомлення про цей стан.

Джерела

Опрацювали матеріал?

Збережіть цей крок у своєму прогресі.

← Повернутися до дисципліни
© 2026 Анастасія Іскандарова-МалаЕлектронний навчальний посібник · ДДТУ

Пошук у посібнику

Спробуйте «C++», «RAII» або «бази даних».