Как пройти собеседование по Kotlin: продвинутые вопросы

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Как пройти собеседование по Kotlin: топ вопросов продвинутого уровня

Собеседование на позицию разработчика Kotlin — это не только проверка синтаксиса. На продвинутом уровне интервьюеры хотят увидеть глубокое понимание языка, его особенностей и умение писать эффективный, лаконичный и безопасный код. В этой статье мы разберем ключевые темы, которые часто встречаются на технических собеседованиях: корутины, inline-функции, Sealed классы, делегирование и хитрости лямбда-выражений. Материал подготовлен для образовательного портала PythonLib, где мы помогаем разработчикам систематизировать знания.

1. Корутины (Coroutines) и Structured Concurrency

Корутины — основа асинхронного программирования в Kotlin. Вопросы по этой теме показывают, понимаете ли вы, как работает планировщик, контекст и обработка исключений.

Что такое Structured Concurrency и зачем он нужен?

Structured Concurrency (структурированная конкурентность) — это принцип, при котором жизненный цикл корутины привязан к области видимости (scope). Если родительская корутина отменяется, все дочерние задачи автоматически отменяются. Это предотвращает утечки памяти и делает код предсказуемым.

fun main() = runBlocking {
    launch {
        delay(1000)
        println("Задача 1 завершена")
    }
    launch {
        delay(500)
        println("Задача 2 завершена")
    }
    println("Главный поток не блокируется")
}

Разница между launch и async

launch запускает корутину, которая не возвращает результат (Unit). async возвращает Deferred, из которого можно получить результат через await(). async удобен для параллельных вычислений.

val deferred1 = async { calculateSum(10, 20) }
val deferred2 = async { calculateSum(30, 40) }
val result = deferred1.await() + deferred2.await()
println("Сумма: $result")

Обработка исключений в корутинах

Используйте try-catch внутри корутины или CoroutineExceptionHandler для глобальной обработки. Важно помнить, что исключения в launch распространяются вверх по иерархии, а в async — хранятся до вызова await().

val handler = CoroutineExceptionHandler { _, exception ->
    println("Ошибка: ${exception.message}")
}

fun main() = runBlocking {
    val job = launch(handler) {
        throw RuntimeException("Что-то пошло не так")
    }
}

2. Inline-функции, reified и кросс-инлайн

Эти механизмы позволяют оптимизировать производительность и работать с дженериками в рантайме. Интервьюеры часто спрашивают про разницу между inline, noinline и crossinline.

Зачем нужны inline-функции?

Inline-функции подставляют тело функции непосредственно в место вызова. Это устраняет накладные расходы на создание лямбда-объектов. Особенно полезно для higher-order функций.

inline fun measureTime(action: () -> Unit) {
    val start = System.currentTimeMillis()
    action()
    println("Время: ${System.currentTimeMillis() - start} мс")
}

Что такое reified?

С reified можно получить информацию о типе внутри inline-функции, что невозможно для обычных дженериков из-за стирания типов.

inline fun <reified T> isInstance(value: Any): Boolean {
    return value is T
}

fun main() {
    println(isInstance<String>("Hello")) // true
}

Разница между noinline и crossinline

  • noinline — запрещает инлайнинг конкретной лямбды (например, если она передается в другую функцию).
  • crossinline — разрешает инлайнинг, но запрещает non-local return (нелокальный возврат) из лямбды, что безопасно для асинхронных вызовов.
inline fun execute(crossinline action: () -> Unit) {
    Thread {
        action() // Ошибка без crossinline, так как lambda с non-local return
    }.start()
}

3. Sealed классы и Sealed интерфейсы

Sealed классы — мощный инструмент для представления ограниченной иерархии типов. Они идеально подходят для моделирования состояний (например, UI-состояний в Android) и для работы с when.

Почему Sealed классы лучше Enum?

В отличие от enum, каждый подкласс sealed может иметь собственные свойства и методы. Это дает гибкость при сохранении строгой типизации.

sealed class NetworkResult<out T> {
    data class Success<T>(val data: T) : NetworkResult<T>()
    data class Error(val message: String) : NetworkResult<Nothing>()
    object Loading : NetworkResult<Nothing>()
}

fun handleResult(result: NetworkResult<String>) {
    when (result) {
        is NetworkResult.Success -> println("Успех: ${result.data}")
        is NetworkResult.Error -> println("Ошибка: ${result.message}")
        NetworkResult.Loading -> println("Загрузка...")
    }
}

Sealed интерфейсы в Kotlin 1.5+

Позволяют создавать иерархию, где подклассы могут быть не только классами, но и объектами или даже другими интерфейсами. Это расширяет возможности моделирования.

sealed interface Payment {
    data class Card(val number: String) : Payment
    data class Cash(val amount: Double) : Payment
    object PayPal : Payment
}

4. Делегирование (Delegation) и Delegated Properties

Kotlin поддерживает делегирование на уровне языка. Это позволяет переиспользовать поведение без наследования.

Делегирование через by

Класс может реализовать интерфейс, делегируя его реализацию другому объекту.

interface Printer {
    fun print(message: String)
}

class ConsolePrinter : Printer {
    override fun print(message: String) = println(message)
}

class Application(printer: Printer) : Printer by printer

fun main() {
    val app = Application(ConsolePrinter())
    app.print("Hello from delegation!") // Вызов делегирован ConsolePr                

Блоги

Рекомендаций по книгам