Kotlin
Baza pytań rekrutacyjnych i wiedzy. Filtruj, szukaj i sprawdzaj swoją wiedzę.
easykotlinvariablesimmutability
Odpowiedź
val oznacza referencję tylko do odczytu, której po inicjalizacji nie można przypisać ponownie. var to referencja mutowalna, którą można nadpisywać. val nie gwarantuje niemutowalności obiektu, ale sprzyja bezpieczniejszemu kodowi.
easydata-classkotlindto+1
Odpowiedź
Data class to klasa w Kotlinie przeznaczona do przechowywania danych. Kompilator automatycznie generuje equals/hashCode, toString, copy() oraz componentN, dlatego świetnie nadaje się na DTO i obiekty wartości.
easynull-safetynullableelvis-operator+1
Odpowiedź
Typy w Kotlinie są domyślnie nie‑null. Aby dopuścić null, oznaczasz typ znakiem ?. Bezpieczne wywołania (?.), operator Elvis (?:) oraz funkcje typu let pomagają obsłużyć wartości null bez NPE. Operator !! wymusza non‑null i może rzucić wyjątek.
val name: String? = null
val length = name?.length ?: 0
println(length)mediumcoroutinesconcurrencysuspend+1
Odpowiedź
Coroutines to lekkie prymitywy współbieżności do kodu asynchronicznego. Mogą się wstrzymywać bez blokowania wątku i są planowane przez runtime Kotlin, więc tysiące coroutine mogą działać na małej puli wątków. Wątki to cięższe byty systemu operacyjnego, które blokują się podczas oczekiwania.
mediumsealed-classenumwhen+1
Odpowiedź
Enumy definiują stały zestaw instancji jednego typu. Sealed class definiuje zamkniętą hierarchię, w której każda podklasa może mieć inny stan. Oba wspierają wyczerpujące when, ale sealed class jest bardziej elastyczna do modelowania złożonych wariantów.
easyvalvarimmutability
Odpowiedź
`val` to referencja tylko-do-odczytu (nie da się jej przepisać), a `var` jest mutowalne (da się przepisać). `val` nie czyni obiektu automatycznie niemutowalnym.
easynull-safetyelvisnullable
Odpowiedź
`T?` to typ nullable, `?.` to bezpieczne wywołanie, `?:` (Elvis) daje wartość domyślną, a `!!` wymusza non-null (rzuca wyjątek, jeśli jest null).
val name: String? = null
val len = name?.length ?: 0
// val crash = name!!.length // throws if name is nulleasydata-classdtovalue-object
Odpowiedź
Generuje `equals/hashCode`, `toString`, `copy` i `componentN` na podstawie primary constructora. Pasuje do DTO/obiektów „wartościowych”, gdzie równość wynika z danych.
mediumscope-functionsletapply+1
Odpowiedź
Różnią się głównie odbiorcą (`this` vs `it`) i wartością zwracaną (obiekt vs wynik lambdy). Przykład: `apply` konfiguruje i zwraca obiekt; `let` przekształca i zwraca wynik lambdy.
val user = User("Ada")
.apply { active = true } // returns receiver
val nameLen = user.name.let { it.length } // returns lambda resultmediumcoroutinesasynclaunch+1
Odpowiedź
`launch` uruchamia coroutine do efektów ubocznych i zwraca `Job`. `async` zwraca `Deferred<T>` z wynikiem i odbierasz go przez `await()`; oba powinny działać w scope (structured concurrency).
coroutineScope {
val a = async { 1 }
val b = async { 2 }
val sum = a.await() + b.await()
println(sum)
}mediumsealedenumwhen
Odpowiedź
Wybierz `sealed`, gdy potrzebujesz zamkniętego zestawu wariantów, które mogą nieść różne dane i typy (sum type). Pozwala to na exhaustive `when`, a `enum` to stały zestaw instancji jednego typu.
sealed interface Result
data class Ok(val value: Int) : Result
data class Err(val message: String) : Result
fun handle(r: Result) = when (r) {
is Ok -> r.value
is Err -> 0
}hardstructured-concurrencycancellationcoroutines
Odpowiedź
To podejście, w którym coroutine są powiązane ze scope, więc ich życie jest ograniczone przez parent job. Anulowanie i błędy propagują się kontrolowanie, co zapobiega „wyciekaniu” pracy w tle (unikaj `GlobalScope` w kodzie aplikacji).
hardflowsequencestreams
Odpowiedź
`Sequence` jest synchroniczna i leniwa na bieżącym wątku. `Flow` to cold asynchroniczny strumień, który może zawieszać, jest anulowalny i jest zbierany w coroutines (`collect`) — dobre do asynchronicznych strumieni danych.
flow {
emit(1)
emit(2)
}.collect { value ->
println(value)
}hardreifiedinlinegenerics
Odpowiedź
Przez type erasure informacja o typie generycznym nie jest dostępna w runtime. `reified` w funkcji `inline` zachowuje typ w miejscu wywołania, więc możesz użyć np. `T::class` albo `value is T`.
inline fun <reified T> Any?.asTypeOrNull(): T? = this as? T
val x: Any = 123
val n: Int? = x.asTypeOrNull<Int>()hardinteropplatform-typesnullability
Odpowiedź
Platform type pochodzi z Javy, gdzie nullability jest nieznane, więc Kotlin traktuje go jako „nullable albo non-null” (`String!`). Jeśli potraktujesz go jako non-null, a w runtime będzie null, nadal możesz dostać NPE; lepiej używać adnotacji nullability i bezpiecznej obsługi.
easylateinitnull-safetyproperties
Odpowiedź
`lateinit` pozwala zainicjalizować non-null `var` później (po konstrukcji). Działa tylko dla mutowalnych pól typów obiektowych (nie dla `val`, nie dla `Int`) i musisz ustawić wartość przed odczytem, inaczej poleci wyjątek.
mediumcoroutinessuspenddispatcher
Odpowiedź
`suspend` oznacza, że funkcja może się wstrzymać bez blokowania wątku i wznowić później. NIE oznacza automatycznie „działa na nowym wątku” — o tym decyduje dispatcher.
suspend fun loadUser(id: String): User {
// can suspend here (e.g., awaiting IO)
return api.getUser(id)
}mediumextensionsdispatchkotlin-basics
Odpowiedź
Pozwalają dodać funkcje do typu bez modyfikowania jego kodu (to cukier składniowy). Typowa pułapka: extension jest wybierany statycznie po typie zadeklarowanym, więc nie działa jak wirtualne override.
open class Base
class Child : Base()
fun Base.say() = "base"
fun Child.say() = "child"
val x: Base = Child()
println(x.say()) // "base" (static dispatch)hardcoroutinesjobsupervisorjob+1
Odpowiedź
Przy zwykłym Job błąd dziecka anuluje rodzica i zwykle cały scope. Przy SupervisorJob (supervisor scope) błąd dziecka nie anuluje rodzeństwa — obsługujesz błąd lokalnie.
hardstateflowsharedflowflow+1
Odpowiedź
StateFlow zawsze ma aktualną wartość i odtwarza ostatnią nowym kolektorom (stan). SharedFlow to bardziej ogólny hot stream: konfigurujesz replay/buffer i używasz go np. do eventów. StateFlow jest do stanu UI, a SharedFlow do zdarzeń jednorazowych.
easylazydelegatesinitialization
Odpowiedź
`by lazy` oblicza wartość przy pierwszym użyciu i potem ją cache’uje. Jest dobre do kosztownej inicjalizacji, której możesz w ogóle nie potrzebować; może być też thread-safe zależnie od trybu.
val config by lazy { loadConfig() }
fun loadConfig(): String = "ok"mediuminlineperformancelambdas
Odpowiedź
`inline` wkleja ciało funkcji w miejsce wywołania. Może zmniejszyć narzut funkcji wyższego rzędu przez unikanie alokacji lambd i wywołań pośrednich (kosztem większego bajtkodu).
mediumdestructuringdata-classcomponentN
Odpowiedź
Destructuring pozwala „rozpakować” obiekt do zmiennych (`val (a, b) = obj`). Działa przez `component1()`, `component2()` itd., które są generowane dla data class (albo mogą być zdefiniowane ręcznie).
hardvalue-classtype-safetydomain
Odpowiedź
Value class opakowuje pojedynczą wartość, dając type-safety bez narzutu obiektu w runtime w wielu przypadkach (może być inline). Przydaje się do silnych typów domenowych typu `UserId` zamiast `String`.
@JvmInline
value class UserId(val value: String)
fun loadUser(id: UserId) = id.valuehardcoroutinescancellationisActive+1
Odpowiedź
Anulowanie nie zatrzymuje magicznie pracy CPU; funkcje zawieszalne sprawdzają anulowanie, ale ciasne pętle muszą współpracować (sprawdzać `isActive` albo wołać `yield()`/`ensureActive()`). Inaczej anulowana coroutine może dalej działać.
easykotlincollectionsimmutability+1
Odpowiedź
`List` jest tylko do odczytu (brak metod add/remove), a `MutableList` pozwala modyfikować. Pamiętaj, że `List` może wskazywać na mutowalną implementację; interfejs tylko ogranicza dostęp.
mediumkotlingenericsvariance+1
Odpowiedź
`out T` oznacza, że typ tylko “produkuje” T (możesz czytać T), więc jest kowariantny (np. `List<out Animal>` może przyjąć `List<Dog>`). `in T` oznacza, że tylko “konsumuje” T (możesz przekazywać T do środka), więc jest kontrawariantny (np. `Comparator<in Dog>`). Dzięki temu kompilator blokuje niebezpieczne odczyty/zapisy.
mediumkotlincoroutinesflow+1
Odpowiedź
`Flow` jest zwykle cold: zaczyna produkować wartości dopiero przy `collect` i świetnie nadaje się do deklaratywnych potoków. `Channel` jest hot i push-based: działa jak asynchroniczna kolejka zdarzeń między korutynami. Flow do strumieni/transformacji, Channel do komunikacji i fan-in/fan-out.
hardkotlincoroutinesdispatchers+1
Odpowiedź
`Dispatchers.Default` jest do pracy CPU-bound. `Dispatchers.IO` jest do blokującego I/O (DB/pliki/klienty sieciowe, które blokują). `Dispatchers.Main` jest do UI. Zasada: nie blokuj `Main` ani `Default`; blokujące rzeczy przenieś na `IO` przez `withContext`.
hardkotlincoroutinesexceptions+1
Odpowiedź
W structured concurrency błąd w dziecku zwykle anuluje rodzica (chyba że użyjesz `SupervisorJob`). W `launch` nieobsłużony wyjątek idzie do rodzica/handlera; w `async` wyjątek jest “schowany” do momentu `await()`. Do lokalnej obsługi używaj `try/catch`, a `CoroutineExceptionHandler` głównie na top-level `launch` (logowanie/obsługa awarii).
easykotlinsmart-casttype-safety+1
Odpowiedź
Smart cast oznacza, że kompilator traktuje zmienną jako bardziej konkretny typ po sprawdzeniu typu (`is`) lub null (`!= null`). Działa, gdy wartość jest stabilna (np. lokalny `val`). Często nie działa dla mutowalnych `var`, open properties albo wartości, które mogą się zmieniać przez custom getter lub współbieżność. Rozwiązanie: przypisz do lokalnego `val` albo użyj jawnego castu (gdy to bezpieczne).
mediumkotlinwhensealed+1
Odpowiedź
`when` może zwracać wartość (jest wyrażeniem). “Exhaustive” oznacza, że obsłużone są wszystkie możliwe przypadki, więc nie potrzebujesz `else`. Dla `enum` i `sealed class` kompilator potrafi to sprawdzić i zmusi Cię do obsłużenia nowych przypadków, gdy typ się rozbuduje.
mediumkotlindelegationby+1
Odpowiedź
Delegacja interfejsu generuje metody przekazujące wywołania do obiektu delegata: `class X(private val d: Foo) : Foo by d`. Zmniejsza boilerplate. Ograniczenie: jeśli chcesz zmienić zachowanie, musisz jawnie nadpisać metody; to nie jest “magiczne dziedziczenie” i nie przechwyci metod, których nie nadpiszesz.
interface Logger { fun log(msg: String) }
class ConsoleLogger : Logger {
override fun log(msg: String) = println(msg)
}
class Service(private val logger: Logger) : Logger by logger {
fun work() = log("working")
}hardkotlintailrecrecursion+1
Odpowiedź
`tailrec` prosi kompilator o zamianę rekurencji ogonowej na pętlę (bez rosnącego stosu wywołań). Działa tylko dla bezpośredniej rekurencji, gdzie wywołanie rekurencyjne jest ostatnią operacją (tail position). Jeśli funkcja nie jest naprawdę ogonowa, kompilator nie zastosuje optymalizacji.
hardkotlininlinecrossinline+2
Odpowiedź
Inlining kopiuje ciało lambdy w miejsce wywołania, więc `return` w lambdzie może zakończyć funkcję zewnętrzną (non-local return). `crossinline` blokuje non-local return, gdy lambda może być wywołana później lub w innym kontekście. `noinline` wyłącza inlining parametru, żeby można było przechować/przekazać lambdę jako wartość.
mediumkotlinextensiondispatch+1
Odpowiedź
Funkcje rozszerzające są wywoływane statycznie na podstawie typu kompilacyjnego, a nie runtime. Nie nadpisują metod członkowskich. Typowy haczyk: wywołanie rozszerzenia na zmiennej o typie bazowym nie uruchomi rozszerzenia z typu pochodnego.
easykotlinobjectsingleton+1
Odpowiedź
Deklaracja `object` tworzy singleton — jedną instancję inicjalizowaną leniwie przy pierwszym użyciu. Stosuje się ją do narzędzi bez stanu lub współdzielonych serwisów, gdy faktycznie potrzebujesz jednej instancji.
easykotlincompaniontop-level+1
Odpowiedź
Funkcje/właściwości top‑level są proste i nie wymagają klasy. Companion object jest powiązany z klasą i przydaje się do metod fabrykujących, stałych „należących” do klasy lub interopu z Javą (`@JvmStatic`).
mediumkotlinlateinitnull-safety+1
Odpowiedź
`lateinit var` stosujesz, gdy potrzebujesz nie‑nullable właściwości inicjalizowanej później (np. w DI lub lifecycle), a `null` nie jest poprawnym stanem. Działa tylko dla `var` i typów nie‑prymitywnych. Inicjalizację sprawdzisz przez `::prop.isInitialized`.
mediumkotlindelegationproperties+1
Odpowiedź
Delegowane właściwości pozwalają wielokrotnie używać logiki get/set (lazy init, obserwacja zmian, walidacja) bez boilerplate. Właściwość delegujesz do obiektu, który dostarcza `getValue`/`setValue`.
var name: String by Delegates.observable("<init>") { _, old, new ->
println("$old -> $new")
}
name = "Ala"mediumkotlinoperatoroverloading+1
Odpowiedź
`operator` pozwala mapować operatory (`+`, `[]`, `in`) na funkcje typu `plus`, `get`, `contains`. Może poprawić czytelność w typach domenowych, ale łatwo przesadzić i uczynić kod nieintuicyjnym.
easykotlintypealiastypes+1
Odpowiedź
`typealias` nadaje nową nazwę istniejącemu typowi, poprawiając czytelność (np. długie typy funkcji). Nie tworzy nowego, odrębnego typu — to tylko alias.
hardkotlinstateflowsharedflow+1
Odpowiedź
StateFlow to hot stream z bieżącą wartością (zawsze ją ma) i replay=1; służy do reprezentacji stanu. SharedFlow to hot stream zdarzeń z konfigurowalnym replay/buforem i może nie mieć bieżącej wartości. StateFlow do stanu, SharedFlow do eventów.
hardkotlincoroutinessupervisor+1
Odpowiedź
W `coroutineScope` błąd jednego childa anuluje cały scope (i pozostałe childy). W `supervisorScope` błąd dziecka nie anuluje rodzeństwa — anulowane jest tylko to dziecko. Używaj supervisorScope, gdy chcesz izolacji między childami.