Kotlin 2.3 dla Androida: dekonstrukcja po nazwach, KMP i pytania rekrutacyjne 2026

Pytania rekrutacyjne z Kotlin 2.3: dekonstrukcja po nazwach, Kotlin Multiplatform, parametry kontekstowe, korutyny i Flow. Przygotowanie do rozmów Android developera w 2026 z praktycznymi przykładami kodu.

Pytania rekrutacyjne Kotlin 2.3 Android z dekonstrukcją i koncepcjami KMP

Pytania rekrutacyjne dotyczące Kotlin 2.3 znalazły się w 2026 roku wśród najczęściej wyszukiwanych tematów dla stanowisk Android developera. Wraz z deklaracjami dekonstrukcji opartymi na nazwach, ustabilizowanymi parametrami kontekstowymi oraz dojrzałym ekosystemem Kotlin Multiplatform, rekruterzy oczekują od kandydatów biegłej znajomości tych funkcji.

Co zmieniło się w Kotlin 2.3

Kotlin 2.3.20, wydany w marcu 2026, wprowadza deklaracje dekonstrukcji oparte na nazwach, zmiany w rozwiązywaniu przeciążeń dla parametrów kontekstowych oraz ulepszoną interoperacyjność KMP z C i TypeScript. Te funkcje pojawiają się często w rozmowach na stanowiska senior Android.

Dekonstrukcja oparta na nazwach: najczęstsze pytanie rekrutacyjne z Kotlin 2.3

Przed wersją Kotlin 2.3 deklaracje dekonstrukcji opierały się na funkcjach componentN() zależnych od pozycji. Tworzyło to dobrze znaną pułapkę: zamiana kolejności właściwości w data class po cichu psuła istniejące miejsca dekonstrukcji.

Klasyczne pytanie rekrutacyjne przedstawia ten scenariusz:

User.ktkotlin
data class User(val username: String, val email: String)

fun main() {
    val user = User("alice", "alice@example.com")
    // Position-based: order matters, not names
    val (email, username) = user
    println(email)    // Prints "alice" -- wrong!
    println(username) // Prints "alice@example.com" -- wrong!
}

Kotlin 2.3.20 rozwiązuje ten problem dzięki dekonstrukcji opartej na nazwach. Trzy tryby kompilatora kontrolują to zachowanie:

build.gradle.ktskotlin
kotlin {
    compilerOptions {
        // Mode 1: Explicit syntax only
        freeCompilerArgs.add("-Xname-based-destructuring=only-syntax")
        // Mode 2: Warns on name mismatch
        // freeCompilerArgs.add("-Xname-based-destructuring=name-mismatch")
        // Mode 3: Full name-based by default
        // freeCompilerArgs.add("-Xname-based-destructuring=complete")
    }
}

W trybie complete nawiasy okrągłe używają dopasowania po nazwach, a nawiasy kwadratowe zachowują zachowanie oparte na pozycji:

NameBasedDestructuring.ktkotlin
data class User(val username: String, val email: String)

fun main() {
    val user = User("alice", "alice@example.com")

    // Name-based: matches by property name
    val (email, username) = user
    println(email)    // "alice@example.com" -- correct
    println(username) // "alice" -- correct

    // Position-based: square brackets preserve old behavior
    val [first, second] = user
    println(first)  // "alice" (component1)
    println(second) // "alice@example.com" (component2)
}

Mocna odpowiedź podczas rozmowy wyjaśnia wszystkie trzy tryby i wskazuje, kiedy każdy z nich ma zastosowanie: only-syntax dla stopniowej migracji, name-mismatch do wychwytywania błędów w istniejącym kodzie oraz complete dla nowych projektów.

Pytania rekrutacyjne o Kotlin Multiplatform dla Android developerów

KMP wyszedł poza fazę eksperymentalną. Google oficjalnie wspiera go w dzieleniu logiki biznesowej między Androidem a iOS, a większość nowych projektów multi-platform domyślnie przyjmuje architekturę współdzielonego rdzenia.

Typowe pytanie rekrutacyjne brzmi: „Jak ustrukturyzowałbyś moduł KMP dla wspólnej sieci?"

shared/src/commonMain/kotlin/com/app/network/ApiClient.ktkotlin
import io.ktor.client.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json

@Serializable
data class UserProfile(
    val id: String,
    val displayName: String,
    val avatarUrl: String?
)

// Expect/actual pattern for platform-specific HTTP engine
expect fun createHttpClient(): HttpClient

class ApiClient {
    private val client = createHttpClient()
    private val json = Json { ignoreUnknownKeys = true }

    // Suspend function shared across Android and iOS
    suspend fun fetchProfile(userId: String): UserProfile {
        val response = client.get("https://api.example.com/users/$userId")
        return json.decodeFromString(response.bodyAsText())
    }
}
shared/src/androidMain/kotlin/com/app/network/HttpClient.android.ktkotlin
import io.ktor.client.*
import io.ktor.client.engine.okhttp.*

// Android uses OkHttp engine
actual fun createHttpClient(): HttpClient = HttpClient(OkHttp) {
    engine {
        config {
            retryOnConnectionFailure(true)
        }
    }
}

Rekruterzy oceniają, czy kandydat rozumie mechanizm expect/actual, zależności specyficzne dla platform oraz granicę między kodem wspólnym a kodem platformy.

Kolejne częste pytanie dotyczy Swift Export, który znacznie ewoluował w 2026 roku. Funkcje suspend w Kotlinie mapują się teraz bezpośrednio na Swift async/await, a klasy zapieczętowane mapują się na enum Swift z wartościami powiązanymi.

Parametry kontekstowe i zmiany w rozwiązywaniu przeciążeń

Parametry kontekstowe zastępują przestarzałe API context receivers. Kotlin 2.3.20 wprowadził istotną zmianę w rozwiązywaniu przeciążeń: deklaracje z parametrami kontekstowymi nie są już traktowane jako bardziej szczegółowe niż te bez nich.

ContextParameters.ktkotlin
class Logger {
    fun info(msg: String) = println("INFO: $msg")
}

class TransactionScope {
    fun execute(block: () -> Unit) = block()
}

// Function with context parameter
context(logger: Logger)
fun saveUser(id: Int) {
    logger.info("Saving user $id")
}

// Overload without context -- now ambiguous in 2.3.20
fun saveUser(id: Int) {
    println("Saving user $id (no logger)")
}

fun main() {
    val logger = Logger()
    // This now produces an ambiguity error in Kotlin 2.3.20
    // context(logger) { saveUser(1) }

    // Fix: rename or qualify the context-aware variant
}

Rekruterzy używają tego tematu, aby ocenić świadomość przełomowych zmian i strategii migracji. Kluczowy wniosek: poleganie na niejawnym rozwiązywaniu parametrów kontekstowych w celu przesłonięcia przeciążeń bez kontekstu już nie działa.

Korutyny i Flow: nadal najważniejszy temat rozmów Android

Pomimo nowych funkcji wprowadzonych w Kotlin 2.3, korutyny i Flow pozostają najczęściej testowanym obszarem. Pytania rekrutacyjne zwykle dotyczą różnicy między zimnymi a gorącymi flow, strukturalnej współbieżności oraz anulowania.

FlowComparison.ktkotlin
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*

// Cold Flow: emits only when collected
fun userUpdates(): Flow<String> = flow {
    println("Flow started") // Runs per collector
    emit("User logged in")
    delay(1000)
    emit("User updated profile")
}

// Hot Flow: emits independently of collectors
class UserRepository {
    // StateFlow holds the latest value
    private val _state = MutableStateFlow("idle")
    val state: StateFlow<String> = _state.asStateFlow()

    // SharedFlow broadcasts to multiple collectors
    private val _events = MutableSharedFlow<String>()
    val events: SharedFlow<String> = _events.asSharedFlow()

    suspend fun login() {
        _state.value = "loading"
        delay(500)
        _state.value = "authenticated"
        _events.emit("login_success")
    }
}

Uzupełniające pytanie często dotyczy bezpiecznego zbierania flow na Androidzie:

UserViewModel.ktkotlin
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch

class UserViewModel(private val repo: UserRepository) : ViewModel() {

    // Expose state to Compose UI
    val uiState: StateFlow<String> = repo.state
        .stateIn(
            scope = viewModelScope,
            started = SharingStarted.WhileSubscribed(5000),
            initialValue = "idle"
        )

    init {
        // Collect events safely within ViewModel scope
        viewModelScope.launch {
            repo.events.collect { event ->
                // Handle one-time events
                println("Event received: $event")
            }
        }
    }
}

Strategia WhileSubscribed(5000) utrzymuje upstream aktywny przez 5 sekund po zniknięciu ostatniego subskrybenta, zapobiegając niepotrzebnym restartom podczas zmian konfiguracji, takich jak obrót ekranu.

Gotowy na rozmowy o Android?

Ćwicz z naszymi interaktywnymi symulatorami, flashcards i testami technicznymi.

Sealed Classes i wyczerpujące when: pattern matching podczas rozmów

Klasy zapieczętowane pojawiają się w niemal każdej rozmowie Android. Pytanie zwykle prosi kandydatów o zamodelowanie stanu interfejsu użytkownika:

UiState.ktkotlin
sealed interface UiState<out T> {
    data object Loading : UiState<Nothing>
    data class Success<T>(val data: T) : UiState<T>
    data class Error(val message: String, val retry: (() -> Unit)? = null) : UiState<Nothing>
}

// Usage in ViewModel
fun loadUsers(): Flow<UiState<List<UserProfile>>> = flow {
    emit(UiState.Loading)
    try {
        val users = apiClient.fetchUsers()
        emit(UiState.Success(users))
    } catch (e: Exception) {
        emit(UiState.Error(
            message = e.localizedMessage ?: "Unknown error",
            retry = { /* trigger reload */ }
        ))
    }
}

W Kotlinie wyrażenia when na typach zapieczętowanych są wyczerpujące w czasie kompilacji. Dodanie nowego podtypu wymusza jego obsługę wszędzie, co eliminuje całą kategorię błędów wykonania. Rekruterzy sprawdzają konkretnie, czy kandydat używa sealed interface (a nie sealed class) dla tego wzorca, ponieważ interfejsy pozwalają na bardziej elastyczne hierarchie.

Kotlin/Wasm i międzyplatformowe cele kompilacji

Kotlin/Wasm osiągnął status Beta i zapewnia wydajność zbliżoną do natywnej w przeglądarkach. Choć nie jest to jeszcze codzienna troska Android developerów, rekruterzy w firmach z produktami webowymi i mobilnymi pytają o niego.

Kluczowe fakty na rozmowę:

  • Kotlin 2.3.20 dostarczył 4,6 razy szybszą interpolację stringów w Wasm dzięki JS String builtins
  • Rozmiary plików binarnych zmalały o około 5%
  • Clean buildy działają o 65% szybciej, buildy inkrementalne o 21% szybciej
  • Adnotacja @nativeInvoke pozwala obiektom Kotlin działać jako funkcje JavaScript

Kotlin/JS zyskał również możliwość implementowania interfejsów Kotlin z TypeScript, usuwając wcześniejsze ograniczenie. W połączeniu ze wsparciem SWC dla transpilacji, Kotlin pokrywa teraz Androida, iOS (przez KMP), Web (przez Wasm/JS) i cele serwerowe z jednej bazy kodu.

Funkcje scope: pozornie proste pytanie filtrujące podczas rozmów

Funkcje scope (let, run, with, apply, also) wydają się proste, ale różnicują doświadczonych deweloperów Kotlin. Pytanie rekrutacyjne zwykle brzmi: „Kiedy używać let zamiast apply?"

ScopeFunctions.ktkotlin
data class Request(
    var url: String = "",
    var method: String = "GET",
    var headers: MutableMap<String, String> = mutableMapOf(),
    var body: String? = null
)

fun buildRequest(): Request {
    // apply: configure an object, returns the object
    return Request().apply {
        url = "https://api.example.com/users"
        method = "POST"
        headers["Content-Type"] = "application/json"
        body = "{\"name\": \"Alice\"}"
    }
}

fun processNullable(input: String?) {
    // let: transform nullable, returns lambda result
    val length = input?.let { value ->
        println("Processing: $value")
        value.trim().length
    } ?: 0

    // also: side effects, returns the original object
    input?.also { println("Logging input: $it") }

    // run: execute a block with receiver, returns lambda result
    val result = input?.run {
        // 'this' is the string
        uppercase().take(10)
    }
}

Framework decyzyjny: apply do konfiguracji obiektu (zwraca odbiorcę), let do przekształceń bezpiecznych dla null (zwraca wynik lambdy), also do efektów ubocznych (zwraca odbiorcę), run do obliczania wyniku w kontekście odbiorcy.

Null safety i niebezpieczeństwo podwójnego wykrzyknika

Null safety pozostaje fundamentalnym tematem rozmów. Pytanie „Dlaczego !! jest niebezpieczne?" sprawdza, czy kandydat naprawdę rozumie system typów Kotlina.

NullSafety.ktkotlin
fun findUser(id: String): User? {
    // Returns null if user not found
    return database.queryUser(id)
}

// Bad: !! defeats the purpose of null safety
fun riskyApproach(id: String) {
    val user = findUser(id)!! // NullPointerException if null
    println(user.username)
}

// Good: handle null explicitly
fun safeApproach(id: String) {
    val user = findUser(id) ?: run {
        println("User not found")
        return
    }
    println(user.username)
}

// Good: provide default with Elvis operator
fun displayName(id: String): String {
    return findUser(id)?.username ?: "Anonymous"
}

Kompletna odpowiedź wspomina, że !! powinno pojawiać się tylko w kodzie testowym lub na granicach interop platformy, gdzie null jest naprawdę niemożliwy.

Zacznij ćwiczyć!

Sprawdź swoją wiedzę z naszymi symulatorami rozmów i testami technicznymi.

Podsumowanie

  • Dekonstrukcja oparta na nazwach w Kotlin 2.3.20 eliminuje błędy zależne od pozycji. Warto znać wszystkie trzy tryby kompilatora: only-syntax, name-mismatch, complete
  • KMP jest stabilny produkcyjnie w 2026 roku. Spodziewaj się pytań o expect/actual, architekturę współdzielonego modułu i interop Swift Export
  • Parametry kontekstowe zastąpiły context receivers. Zmiana w rozwiązywaniu przeciążeń w 2.3.20 to typowe pytanie podchwytliwe
  • Korutyny i Flow dominują w rozmowach Android. Opanuj StateFlow vs SharedFlow, WhileSubscribed oraz strukturalną współbieżność
  • Sealed interface modelują stan UI z wyczerpującością na etapie kompilacji. Preferuj sealed interface zamiast sealed class
  • Funkcje scope wymagają jasnego frameworka decyzyjnego: odbiorca vs argument, wartość zwracana vs odbiorca
  • Null safety nie podlega negocjacji. Wyjaśnij, dlaczego !! podważa system typów i pokaż alternatywy

Zacznij ćwiczyć!

Sprawdź swoją wiedzę z naszymi symulatorami rozmów i testami technicznymi.

Tagi

#kotlin
#android
#interview
#kmp
#kotlin-multiplatform
#coroutines

Udostępnij

Powiązane artykuły