---
title: "Kotlin Core – Основни въпроси"
url: "https://romankryvolapov.com/bg/kotlin-core-questions/"
description: "Как са устроени корутините и suspend, с какво launch се различава от async, как работят scope functions, делегатите, extension и companion object в Kotlin."
language: bg
published: 2019-09-16
updated: 2026-11-13
---
## Какво са вътрешните класове (Inner Classes) в Kotlin

В Kotlin вложените класове (nested classes) са класове, които са декларирани вътре в друг клас. Ако такъв клас е означен с ключовата дума inner, той става вътрешен (inner class), има достъп до членовете на външния клас и може да се използва за реализиране на шаблони за проектиране, като „Стратегия“ или „Наблюдател“.

В Kotlin вложеният клас по подразбиране не пази препратка към външния клас (като статичен вложен клас в Java, за разлика от обикновения вложен клас в Java). Тоест той няма достъп до членовете на външния клас и може да бъде създаден без създаване на екземпляр на външния клас.

Но ако вложеният клас е означен с ключовата дума 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"
}
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)
