Как пройти собеседование по 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