Irbisa · Шпаргалка

Основы Kotlin

val/var, типы, null safety, control flow, функции, лямбды

Junior-разработчик · 24 вопроса

  1. 01

    Какие преимущества у Kotlin перед Java в Android-разработке и какие у него недостатки?

    Kotlin убирает целые классы багов и бойлерплейта Java (nullability в системе типов, data-классы, корутины) и при этом работает на той же JVM и напрямую вызывает Java; плата за это: время сборки, скрытые накладные расходы в рантайме и слабое место на границе с Java.

  2. 02

    В чём разница между val, var и const val в Kotlin?

    val объявляет ссылку только для чтения, присваиваемую один раз, var можно переприсвоить, а const val объявляет константу времени компиляции, значение которой компилятор подставляет в каждое место использования.

    val name = "Ada"                  // type inferred as String// name = "Bob"                   // compile error: val cannot be reassigned var attempts = 0attempts += 1                     // fine…
  3. 03

    Как работает null safety в Kotlin: nullable-типы, ?., ?:, !! и let?

    Nullability входит в тип: String никогда не содержит null, String? может, и компилятор не даст использовать String? как String, пока вы не обработали случай null.

    fun greet(user: User?) {    val city = user?.address?.city          // String?: null if any link is null    val name = user?.name ?: "Guest"        // fallback value    user?.let { analytics.track(it.id) }    // runs only when user != null     if (user == null) return…
  4. 04

    Что напечатает describe(null) в этом коде, построчно?

    fun describe(name: String?) {    println(name?.length)    println(name?.length ?: -1)    println(name.isNullOrEmpty())    println("Hello, $name")    println(name!!.length)} fun main() {    describe(null)}

    Код напечатает null, -1, true и Hello, null, а затем последняя строка бросит NullPointerException: падает только !!, все остальные операторы здесь обрабатывают null намеренно.

  5. 05

    lateinit или by lazy: когда что использовать и как проверить, что lateinit-свойство инициализировано?

    lateinit var нужен для non-null значения, которое позже присвоит кто-то другой, например Hilt или onCreate; by lazy нужен для val, который вычисляет себя при первом чтении и кеширует результат.

    @AndroidEntryPointclass ProfileActivity : AppCompatActivity() {    @Inject lateinit var repo: UserRepository        // assigned by Hilt    private lateinit var binding: ActivityProfileBinding     private val dateFormat by lazy {                 // created on first use…
  6. 06

    В чём разница между == и === в Kotlin?

    == проверяет структурное равенство через вызов equals(), а === проверяет ссылочное: указывают ли обе ссылки на один и тот же объект.

    data class User(val id: Int, val name: String) val a = User(1, "Ada")val b = User(1, "Ada")println(a == b)      // true: equals() compares propertiesprintln(a === b)     // false: two different objects…
  7. 07

    Как when работает как выражение в Kotlin и когда он обязан быть исчерпывающим?

    when играет в Kotlin роль switch, но он является выражением и возвращает значение сработавшей ветки; если это значение используется, ветки должны покрывать все входные данные: через else или перечислением всех вариантов enum, sealed-типа или Boolean.

    sealed interface UiState {    data object Loading : UiState    data class Content(val items: List<String>) : UiState    data class Error(val message: String) : UiState}…
  8. 08

    Как работают диапазоны в Kotlin: .., ..<, until, downTo и step?

    Диапазон start..end включает оба конца; ..< и until исключают конец, downTo считает в обратную сторону, а step меняет шаг.

    val items = listOf("a", "b", "c") // for (i in 0..items.size) println(items[i])   // wrong: crashes when i == 3for (i in 0..<items.size) println(items[i])     // end excludedfor (i in 0 until items.size) println(items[i]) // same, older spellingfor (i in items.indices) println(items[i])…
  9. 09

    Что такое лямбды и функции высшего порядка в Kotlin и во что компилируется лямбда?

    Лямбдой называют функциональный литерал вроде { x: Int -> x * 2 }, который можно сохранить или передать; функция высшего порядка принимает функцию параметром или возвращает её, как filter, map или ваша собственная retry(block: () -> Boolean).

    // Higher-order function: takes a function as a parameterfun retry(times: Int, block: () -> Boolean): Boolean {    repeat(times) { if (block()) return true }    return false}…
  10. 10

    Что напечатает этот код со счётчиком и где живёт count после того, как makeCounter() вернула управление?

    fun makeCounter(): () -> Int {    var count = 0    return { ++count }} fun main() {    val first = makeCounter()    val second = makeCounter()    println(first())    println(first())    println(second())}

    Код напечатает 1, 2, 1: каждый вызов makeCounter() создаёт свой count, а возвращённая лямбда его захватывает, поэтому у first и second отдельные счётчики, которые переживают вызов функции.

  11. 11

    Как работают аргументы по умолчанию и именованные аргументы в Kotlin и как они заменяют паттерн Builder?

    Параметр может объявить значение по умолчанию, и вызывающий код передаёт только то, что отличается, а любой аргумент можно передать по имени; вместе они заменяют телескопические конструкторы и большинство рукописных Builder-ов.

    data class Request(    val url: String,    val method: String = "GET",    val timeoutMs: Long = 10_000,    val headers: Map<String, String> = emptyMap(),)…
  12. 12

    В чём разница между List и MutableList, а также между val с изменяемым списком и var со списком только для чтения?

    List является интерфейсом только для чтения без add и remove, MutableList их добавляет; val или var остаётся отдельным выбором: он решает, может ли переменная указывать на другой список, а не может ли меняться сам список.

    class Cart {    private val _items = mutableListOf<String>()   // mutable inside    val items: List<String> get() = _items         // read-only outside     fun add(item: String) { _items += item }}…
  13. 13

    Что делают map, flatMap, fold, reduce, groupBy, associateBy, partition и zip над коллекцией в Kotlin?

    Это расширения стандартной библиотеки, которые строят новую коллекцию или значение, не меняя исходную: map преобразует каждый элемент, fold/reduce сворачивают все элементы в одно значение, а остальные перегруппировывают элементы в списки, мапы или пары.

    data class Order(val id: Int, val user: String, val total: Long, val items: List<String>) val orders = listOf(    Order(1, "ada", 300, listOf("pen", "ink")),    Order(2, "bob", 0, emptyList()),    Order(3, "ada", 150, listOf("paper")),…
  14. 14

    Когда использовать Array вместо List и почему arrayOf(1, 2) == arrayOf(1, 2) возвращает false?

    Массивы компилируются в JVM-массивы фиксированного размера, которые сравниваются по ссылке, поэтому == для двух массивов с одинаковым содержимым даёт false; по умолчанию берите List, а массивы берите только там, где их требует API, vararg или производительность на примитивах.

    val a = arrayOf(1, 2)val b = arrayOf(1, 2)println(a == b)                        // false: reference comparisonprintln(a.contentEquals(b))            // trueprintln(a.contentToString())           // [1, 2]println(listOf(1, 2) == listOf(1, 2))  // true…
  15. 15

    Как работают деструктурирующие объявления в Kotlin?

    val (a, b) = obj по порядку вызывает obj.component1() и obj.component2(); имена, которые вы пишете, не важны, важна только позиция.

    data class Size(val width: Int, val height: Int) val (w, h) = Size(1080, 1920)        // component1(), component2() val prices = mapOf("apple" to 3, "pear" to 5)for ((fruit, price) in prices) println("$fruit costs $price")…
  16. 16

    Почему сборка строки через += в цикле медленная и что использовать вместо неё?

    String неизменяем, поэтому каждый += создаёт новую строку и копирует все символы, накопленные к этому моменту, так что n добавлений стоят O(n²); StringBuilder, обычно через buildString { } или joinToString(), дописывает в один растущий буфер.

    // wrong: a new String on every iteration, O(n²) copyingvar csv = ""for (u in users) csv += u.name + "," // right: one bufferval csvFast = buildString {…
  17. 17

    Что такое smart cast в Kotlin, когда компилятор от него отказывается и чем отличаются as и as??

    После проверки вроде x is String или x != null компилятор считает x более узким типом в этой ветке без явного приведения, но только если может доказать, что значение не изменится между проверкой и использованием.

    fun describe(x: Any): String {    if (x is String) return "text of ${x.length}"  // smart cast to String    if (x !is Int) return "unknown"    return "number ${x + 1}"                       // smart cast to Int}…
  18. 18

    Найдите баг: залогиненный пользователь, впервые открывший профиль, тоже видит баннер для гостей.

    data class User(val id: String, val name: String) class ProfileScreen {    private val lastSeenName = mutableMapOf<String, String>()     fun render(user: User?) {        user?.let { lastSeenName.put(it.id, it.name) } ?: showGuestBanner()        showGreeting(user?.name ?: "guest")    }     private fun showGuestBanner() = println("Sign in to sync your progress")    private fun showGreeting(name: String) = println("Hello, $name")}

    ?.let { } ?: showGuestBanner() проверяет результат лямбды, а не user: lastSeenName.put() возвращает предыдущее значение, а при первом заходе оно null, поэтому баннер для гостя видит и залогиненный пользователь.

  19. 19

    Может ли val-свойство возвращать разное значение при каждом чтении?

    Да, val означает лишь отсутствие сеттера; у val с кастомным геттером нет backing field, и геттер выполняется при каждом чтении, так что он может каждый раз возвращать новое значение.

    class Basket {    private val items = mutableListOf<Item>()     val size: Int get() = items.size        // no backing field, recomputed    val isEmpty: Boolean get() = size == 0…
  20. 20

    Найдите баг: после того как один пользователь вышел, а другой вошёл, все запросы падают с 401 до перезапуска приложения.

    data class Request(val path: String, val headers: Map<String, String>) class Session {    var token: String? = null              // replaced on every login} class ApiClient(private val session: Session) {  // one instance for the app    private val authHeader by lazy { "Bearer ${session.token}" }     fun request(path: String): Request =        Request(path, headers = mapOf("Authorization" to authHeader))}

    by lazy вычисляет authHeader один раз, при первом запросе, и кеширует его на всё время жизни единственного ApiClient, поэтому после повторного входа каждый запрос по-прежнему уходит с токеном предыдущего пользователя, и сервер отвечает 401.

  21. 21

    Есть ли в Kotlin примитивные типы и когда Int боксируется?

    Отдельных примитивных типов в языке Kotlin нет (Int выглядит как обычный класс), но компилятор хранит non-null Int, Long, Double или Boolean как JVM-примитив везде, где может, и боксирует его, только когда нужен объект.

    val count: Int = 42                 // int on the JVMval maybe: Int? = 42                // java.lang.Integer: nullable means boxedval list: List<Int> = listOf(1, 2)  // List<Integer>: generics always boxval raw = IntArray(1_000)           // int[], no boxing // val total: Long = count          // compile error: no implicit widening…
  22. 22

    Что такое Any, Unit и Nothing в Kotlin и сколько экземпляров каждого может существовать?

    Any служит корнем всех non-null типов, экземпляров у него сколько угодно, у Unit ровно один экземпляр и он играет роль void из Java, а у Nothing экземпляров нет вовсе: это тип выражения, которое никогда не завершается.

    fun log(message: String) {           // returns Unit implicitly    println(message)}val onDone: () -> Unit = { log("done") } fun fail(message: String): Nothing = throw IllegalArgumentException(message)…
  23. 23

    Есть ли в Kotlin checked exceptions и как тогда сообщать об ошибках и обрабатывать их?

    Нет, ничто в сигнатуре Kotlin-функции не заставляет вызывающий код ловить исключение, поэтому ожидаемые ошибки уходят в тип возврата (sealed-результат или Result<T>), а исключения остаются для багов и действительно исключительных ситуаций.

    sealed interface LoginResult {    data class Success(val user: User) : LoginResult    data object WrongPassword : LoginResult    data object Offline : LoginResult}…
  24. 24

    Что напечатает этот код с forEach и как сделать, чтобы он пропускал только пробельный элемент?

    fun printVisible(items: List<String>) {    items.forEach {        if (it.isBlank()) return        println(it)    }    println("done")} fun main() {    printVisible(listOf("a", " ", "b"))}

    Код напечатает только a: forEach объявлена как inline-функция, поэтому голый return внутри её лямбды возвращает из самой printVisible, и ни b, ни done не печатаются.