---
title: "Kotlin Core — Основні питання"
url: "https://romankryvolapov.com/uk/kotlin-core-questions/"
description: "Як влаштовані корутини та suspend, чим launch відрізняється від async, як працюють scope functions, делегати, extension і companion object у Kotlin."
language: uk
updated: 2026-01-21
---
## Що таке внутрішні класи (Inner Classes) у Kotlin

У Kotlin внутрішні класи (inner classes) — це класи, які оголошені всередині іншого класу. Вони мають доступ до членів зовнішнього класу і можуть використовуватися для реалізації патернів проєктування, таких як “Стратегія” або “Спостерігач”.

На відміну від Java, у Kotlin внутрішні класи за замовчуванням є статичними. Тобто вони не мають доступу до нестатичних членів зовнішнього класу і можуть бути створені без створення екземпляра зовнішнього класу.

Однак, якщо внутрішній клас позначено ключовим словом inner, то він стає нестатичним, і тоді він отримує доступ до нестатичних членів зовнішнього класу та потребує екземпляра зовнішнього класу для створення.\
Приклад внутрішнього класу в Kotlin:

```kotlin
class Outer {
    private val outerField = "Outer field"
    inner class Inner {
        fun innerMethod() {
            println("Accessing outer field: $outerField")
        }
    }
}

fun main() {
    val outer = Outer()
    val inner = outer.Inner()
    inner.innerMethod() // Виведе "Accessing outer field: Outer field"
}
```

## Які є функції області видимості (Scope Functions) у Kotlin

У Kotlin є п'ять функцій області видимості (Scope Functions), які дозволяють змінювати область видимості змінних, а також спрощують читання коду і зменшують імовірність помилок:

**let:**\
дозволяє виконати блок коду на об'єкті, переданому як аргумент, і повернути результат цього блоку. Усередині блоку можна використовувати посилання на об'єкт через it.

```kotlin
val result = someObject?.let { it.property } ?: defaultValue
```

**run:**\
виконує блок коду на об'єкті, переданому як this, і повертає результат цього блоку. Усередині блоку можна використовувати посилання на об'єкт через this.

```kotlin
val result = someObject?.run { property } ?: defaultValue
```

**with:**\
виконує блок коду, який передає об'єкт як аргумент, і повертає результат цього блоку.

```kotlin
val result = with(someObject) { property } ?: defaultValue
```

**apply:**\
виконує блок коду на об'єкті, переданому як this, і повертає цей об'єкт. Усередині блоку можна використовувати посилання на об'єкт через this.

```kotlin
val someObject = SomeClass().apply {
    property1 = value1
    property2 = value2
}
```

**also:**\
дозволяє виконати блок коду на об'єкті, переданому як аргумент, і повернути цей об'єкт. Усередині блоку можна використовувати посилання на об'єкт через it.

```kotlin
val someObject = SomeClass().also {
    it.property1 = value1
    it.property2 = value2
}
```

їх можна комбінувати

```kotlin
variable?.let { змінна не null } ?: run { змінна null }
// те саме, що
if(variable != null) { змінна не null } else { змінна null }
```

## Як працює корутина і що таке suspend

**Звичайна функція:**\
не мають стану і завжди запускаються «з чистого аркуша» (якщо, звісно, не використовують глобальні змінні)\
має завершити своє виконання, перш ніж повернути керування коду, який її викликав

**Корутина:**\
має стан і може призупиняти та відновлювати своє виконання в певних точках, повертаючи, таким чином, керування ще до завершення свого виконання.\
Корутина не прив'язана до нативного потоку, вона може бути призупинити виконання в одному потоці, а відновити виконання в іншому, у корутинах немає власного стека, не потребує перемикання контексту процесора, тому працює швидше.

**Є дві функції для запуску корутини:** \
launch{} і async{}.

**launch{}:** \
нічого не повертає

**async{}:** \
повертає екземпляр Deferred, у якому є функція await(), що повертає результат корутини

**CoroutineScope:** \
відстежує будь-яку корутину, створену за допомогою launch або async (це функції розширення в CoroutineScope).\
Поточну роботу (запущені корутини) може бути скасовано викликом scope.cancel() у будь-який момент часу.

**Job:** \
елемент, що керує корутиною. Для кожної створюваної корутини (за допомогою launch або async) він повертає екземпляр Job, який однозначно ідентифікує корутину і керує її життєвим циклом. Job може проходити через безліч станів: нове, активне, завершення, завершене, скасування і скасоване. Хоча в нас немає доступу до самих станів, ми можемо отримати доступ до властивостей Job: isActive, isCancelled та isCompleted.

**CoroutineContext:** \
це набір елементів, що визначають поведінку корутини.\
**Він складається з:**\
**Job:** керує життєвим циклом корутини.\
**CoroutineDispatcher:** надсилає роботу у відповідний потік.\
**CoroutineName:** ім'я корутини, корисно для налагодження.\
**CoroutineExceptionHandler:** обробляє невловлені винятки, які будуть розглянуті в 3 частині серії про корутини.\
У корутинах є delay(1000L)- не блокуюча затримка

```kotlin
suspend fun doSomeWork() = coroutineScope {
        launch { doWork() }
    }
suspend fun doWork() {
        println("before")
        delay(400L)
        println("after")
    }
```

Оскільки в цій функції застосовується функція delay(), то doWork() визначена з модифікатором suspend. Сама корутина створюється також за допомогою функції launch(), яка викликає функцію doWork().\
При виклику затримки за допомогою функції delay(), ця корутина звільняє потік, у якому вона виконувалася, і зберігається в пам'яті. А звільнений потік може бути задіяний для інших завдань. А коли завершується запущене завдання (наприклад, виконання функції delay()), корутина відновлює свою роботу в одному з вільних потоків.

## Яка різниця між методом запуску корутин launch і async

Є дві функції для запуску корутини: launch{} і async{}.

**launch{}:**\
нічого не повертає,

```kotlin
fun main(args: Array<String>) {
    print("1 ")
    val job: Job = GlobalScope.launch {
        print("3 ")
        delay(1000L)
        print("4 ")
    }
    print("2 ")
// 1 2 але далі не виконається, оскільки програма завершиться раніше
// або можна зробити job.cancel()
}
```

**async{}:**\
повертає екземпляр Deferred, у якому є функція await(), що повертає результат корутини, прямо як Future у Java, де ми робимо future.get() для отримання результату.

```kotlin
suspend fun main(args: Array<String>) {
    print("1 ")
    val deferred: Deferred<String> = GlobalScope.async {
        return@async "3 "
    }
    print("2 ")
    print(deferred.await())
    print("4 ")
    // 1 2 3 4
}
```

## Що таке Делегати в Kotlin

**Делегування класів:**\
Ключове слово by в заголовку Derived, що знаходиться після типу делегованого класу, говорить про те, що об'єкт b типу Base буде зберігатися всередині екземпляра Derived, і компілятор згенерує в Derived відповідні методи з Base, які при виклику будуть передані об'єкту b

```kotlin
interface Base {
    fun print()
}
class BaseImpl(val x: Int) : Base {
    override fun print() { print(x) }
}
class Derived(b: Base) : Base by b
fun main(args: Array<String>) {
    val b = BaseImpl(10)
    Derived(b).print() // prints 10
}
```

**Делегування властивостей:**\
Існує кілька основних видів властивостей, які ми реалізовуємо щоразу вручну в разі їх потреби. Однак набагато зручніше було б реалізувати їх раз і назавжди та покласти в якусь бібліотеку. Приклади таких властивостей:\
ліниві властивості (lazy properties): значення обчислюється один раз, при першому зверненні\
властивості, на події про зміну яких можна підписатися (observable properties)\
властивості, що зберігаються в асоціативному списку, а не в окремих полях\
Для таких випадків Kotlin підтримує делеговані властивості.\
Вираз після by — делегат: звернення (get(), set()) до властивості будуть оброблятися цим виразом. Делегат не зобов'язаний реалізовувати якийсь інтерфейс, достатньо, щоб у нього були методи getValue() і setValue() з певною сигнатурою

```kotlin
class Example {
    var p: String by Delegate()
}
class Delegate {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        return "$thisRef, дякую за делегування мені '${property.name}'!"
    }

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
        println("$value було присвоєно значенню '${property.name} у $thisRef.'")
    }
}
```

**Стандартні делегати:**

**Ліниві властивості (lazy properties):**\
перший виклик get() запускає лямбда-вираз, переданий lazy() як аргумент, і запам'ятовує отримане значення, а наступні виклики просто повертають обчислене значення.

```kotlin
val lazyValue: String by lazy {
    println("computed!")
    "Hello"
}
val lazyValue: String by lazy {
    println("computed!")
    "Hello"
}
fun main(args: Array<String>) {
    println(lazyValue) // computed! Hello
    println(lazyValue) // Hello
}
```

**Observable властивості:**\
Функція Delegates.observable() приймає два аргументи: початкове значення властивості та обробник (лямбда), який викликається при зміні властивості. У обробника три параметри: опис властивості, яка змінюється, старе значення і нове значення.

```kotlin
class User {
    var name: String by Delegates.observable("<no name>") {
        prop, old, new ->
        println("$old -> $new")
    }
}
fun main(args: Array<String>) {
    val user = User()
    user.name = "first"
    user.name = "second"
}
// вивід
// <no name> -> first
// first -> second
```

**Зберігання властивостей в асоціативному списку:**

```kotlin
class User(val map: Map<String, Any?>) {
    val name: String by map
    val age: Int     by map
}
val user = User(mapOf(
    "name" to "John Doe",
    "age"  to 25
))
```

## Що таке Extension у Kotlin

Kotlin дозволяє розширювати клас шляхом додавання нового функціоналу.\
Розширення насправді не проводять жодних модифікацій з класами, які вони розширюють. Оголошуючи розширення, ви створюєте нову функцію, а не новий член класу. Такі функції можуть бути викликані через крапку, застосовно до конкретного типу.\
Розширення мають статичну диспетчеризацію: це означає, що викликана функція-розширення визначається типом її виразу під час компіляції, а не типом виразу, обчисленим у ході виконання програми, як при виклику віртуальних функцій.

```kotlin
fun Any?.toString(): String {
    if (this == null) return "null"
    return toString()
}
```

## Що таке companion object у Kotlin

Щось на кшталт заміни статики в Java, до них звертаються через назву класу. Такі члени допоміжних об'єктів виглядають як статичні члени в інших мовах програмування. Насправді ж вони є членами реальних об'єктів і можуть реалізовувати, наприклад, інтерфейси:

```kotlin
interface Factory<T> {
    fun create(): T
}
class MyClass {
    companion object : Factory<MyClass> {
        override fun create(): MyClass = MyClass()
    }
}
```

## Як використовувати гетери (get) і сетери (set) у Kotlin

```kotlin
var stringRepresentation: String
    get() = this.toString()
    set(value) {
      setDataFromString(value)
    }
var setterVisibility: String = "abc"
    private set // сетер має private доступ і стандартну реалізацію
```

## Що таке анотація @JvmStatic

Вказує, що з цього елемента має бути згенеровано додатковий статичний метод,якщо це функція.Якщо цей елемент є властивістю,то мають бути згенеровані додаткові статичні методи отримання/встановлення.

## Що таке зворотний виклик (callback), функціональні типи та Unit у Kotlin і як використовується

Unit те саме, що й void у Java, не повертає нічого.

```kotlin
fun main(args: Array<String>) {
    one { variable ->
        print(variable)
    }
    two { variable ->
        print(variable)
        return@two "4 "
    }
    three {
        return@three "5 "
    }
    four({ variableOne ->
        print(variableOne)
    }, { variableTwo ->
        print(variableTwo)
    })
    val listenerOne: (() -> Unit) = {
        print("8 ")
    }
    listenerOne.invoke()
    val listenerTwo: ((String) -> Unit) = { variable ->
        print(variable)
    }
    listenerTwo.invoke("9 ")
    val listenerThree: ((String) -> String) = { variable ->
        variable
    }
    val result = listenerThree.invoke("10 ")
    print(result)
}
fun one(callback: (String) -> Unit) {
// передаємо в лямбду і передаємо в неї String, нічого не очікуємо назад (Unit)
    callback("1 ")
}
fun two(callback: (String) -> String) {
// передаємо в лямбду і передаємо в неї String, очікуємо назад String
    callback("2 ")
    print(callback("3 "))
}
fun three(callback: () -> String) {
// викликаємо лямбду, але нічого в неї не передаємо, очікуємо назад String
    print(callback())
}
fun four(callbackOne: (String) -> Unit, callbackTwo: (String) -> Unit) {
// повертає 2 колбеки
    callbackOne("6 ")
    callbackTwo("7 ")
}
// 1 2 3 4 5 6 7 8 9 10
```

**Функціональні типи:**\
Kotlin використовує сімейство функціональних типів, таких як (Int) -> String, для оголошень, які є частиною функцій: val onClick: () -> Unit = ….

Усі функціональні типи мають список з типами параметрів, взятий у дужки, і тип, що повертається: (A, B) -> C позначає тип, який надає функції два прийняті аргументи типу A і B, а також повертає значення типу C. Список з типами параметрів може бути порожнім, як, наприклад, в () -> A. Тип Unit, що повертається, не може бути опущений.

Функціональні типи можуть мати додатковий тип отримувач (receiver), який вказується в оголошенні перед крапкою: тип A.(B) -> C описує функції, які можуть бути викликані для об'єкта-отримувача A з параметром B і значенням C, що повертається. Літерали функцій з об'єктом-приймачем часто використовуються разом із цими типами.

Зупинювані функції (suspending functions) належать до особливого виду функціональних типів, у яких в оголошенні присутній модифікатор suspend, наприклад, suspend () -> Unit або suspend A.(B) -> C.

Оголошення функціонального типу також може включати іменовані параметри: (x: Int, y: Int) -> Point. Іменовані параметри можуть бути використані для опису змісту кожного з параметрів.

Щоб указати, що функціональний тип може бути nullable, використовуйте круглі дужки: ((Int, Int) -> Int)?.

За допомогою круглих дужок функціональні типи можна об'єднувати: (Int) -> ((Int) -> Unit).

Стрілка в оголошенні є правоасоціативною (right-associative), тобто оголошення (Int) -> (Int) -> Unit еквівалентне оголошенню з попереднього прикладу, а не ((Int) -> (Int)) -> Unit.

Ви також можете присвоїти функціональному типу альтернативне ім'я, використовуючи псевдоніми типів:\
typealias ClickHandler = (Button, ClickEvent) -> Unit

**Створення функціонального типу:**\
Існує кілька способів отримати екземпляр функціонального типу:\
Використовуючи блок з кодом усередині функціонального літерала в одній із форм:\
лямбда-вираз: { a, b -> a + b },\
анонімна функція: fun(s: String): Int { return s.toIntOrNull() ?: 0 }\
Використовуючи екземпляр користувацького класу, який реалізує функціональний тип як інтерфейс:

```kotlin
class IntTransformer: (Int) -> Int {
 override operator fun invoke(x: Int): Int = TODO()
}
val intFunction: (Int) -> Int = IntTransformer()
```

[Copyright: Roman Kryvolapov](https://t.me/RomanKryvolapov)
