Kotlin: run, let, apply, also, with — когда что использовать? Примеры

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

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

Начать курс

Kotlin: run, let, apply, also, with — когда что использовать? Полный разбор с примерами

Привет, друг! Если ты изучаешь Kotlin и хочешь писать чистый, лаконичный код, то рано или поздно столкнёшься с пятью волшебными функциями: run, let, apply, also и with. Это scope functions (функции области видимости) — они позволяют выполнять код в контексте объекта и здорово упрощают жизнь.

Но вот незадача: многие новички (и не только) путаются, когда что применять. Этот вопрос — один из самых популярных на Stack Overflow, и сегодня мы раз и навсегда разберёмся, как не ошибиться. Мы не просто перечислим правила — мы покажем реальные примеры из практики, которые ты сможешь сразу использовать в своих проектах.

Что такое scope functions и зачем они нужны?

Scope functions — это функции, которые создают временную область видимости для объекта. Внутри этой области ты можешь обращаться к объекту через it или this. Главное отличие между ними — как они возвращают результат и как ссылаются на контекст.

Вот краткая шпаргалка, которую мы будем наполнять примерами:

  • let — контекст: it, возвращает: результат лямбды. Используется для ненулевых проверок и цепочек вызовов.
  • run — контекст: this, возвращает: результат лямбды. Универсальная функция, часто для инициализации и вычислений.
  • apply — контекст: this, возвращает: сам объект. Идеален для настройки свойств объекта.
  • also — контекст: it, возвращает: сам объект. Хорош для дополнительных действий (логирование, проверки).
  • with — не extension-функция, контекст: this, возвращает: результат лямбды. Удобен для группировки вызовов одного объекта без создания нового.

Запомни главное правило: выбор функции зависит от того, что тебе нужно — вернуть объект или результат, и как ты хочешь обращаться к контексту (через it или this).

1. let — король безопасных цепочек

let — это твой лучший друг, когда нужно работать с nullable объектами. Он выполняет код только если объект не равен null. Контекст передаётся как it.

Пример: безопасное преобразование

val name: String? = "Андрей"
// Без let:
if (name != null) {
    println("Длина имени: ${name.length}")
}

// С let (гораздо элегантнее):
name?.let {
    println("Длина имени: ${it.length}")
}

let также отлично подходит для цепочек операций. Например, когда нужно преобразовать один объект в другой:

val numbers = listOf(1, 2, null, 4, null, 6)
val nonNullSquared = numbers
    .mapNotNull { it }
    .let { list ->
        list.map { it * it }
    }
println(nonNullSquared) // [1, 4, 16, 36]

Когда использовать let:

  • Проверка на null (самый частый кейс).
  • Преобразование одного значения в другое.
  • Выполнение цепочки операций с результатом.

2. apply — конструктор без лишних слов

apply возвращает сам объект, а внутри лямбды контекст — this. Это идеальный инструмент для настройки объекта перед использованием. Забудь про длинные блоки set!

Пример: настройка View (Android) или data class

data class User(var name: String = "", var age: Int = 0)

val user = User().apply {
    name = "Мария"
    age = 25
}
println(user) // User(name=Мария, age=25)

// Ещё круче — настройка TextView:
val textView = TextView(context).apply {
    text = "Привет, Kotlin!"
    textSize = 18f
    setTextColor(Color.BLUE)
}

Обрати внимание: мы не пишем this.name — Kotlin сам подставляет контекст. apply возвращает настроенный объект, что позволяет сразу присвоить его переменной.

Когда использовать apply:

  • Инициализация свойств объекта (особенно UI-компонентов).
  • Построение объекта с множеством параметров.
  • Работа с Builder-паттерном (например, AlertDialog).

3. also — шпион, который не мешает

also похож на apply, но контекст — it. Он тоже возвращает объект, но используется для побочных эффектов: логирование, отладка, валидация — всё то, что не меняет сам объект.

Пример: логирование при цепочке вызовов

val numbers = mutableListOf(1, 2, 3)
numbers
    .also { println("Исходный список: $it") }
    .add(4)
    .also { println("После добавления: $numbers") }
    .let { numbers.filter { it > 2 } }
    .also { println("Отфильтрованные: $it") }

Видишь магию? also позволяет вставить логирование, не разрывая цепочку. Это гораздо удобнее, чем писать отдельные println.

Когда использовать also:

  • Логирование и отладка.
  • Дополнительные проверки (например, перед сохранением).
  • Любые действия, которые не должны влиять на основной поток.

4. run — универсальный солдат

run — это гибрид: он и контекст this, и возвращает результат лямбды. Очень удобен, когда нужно выполнить несколько операций с объектом и вернуть какой-то итог.

Пример: вычисление на основе объекта

val user = User("Иван", 30)
val description = user.run {
    "Пользователь: $name, возраст: $age"
}
println(description) // Пользователь: Иван, возраст: 30

// А ещё run отлично работает с nullable:
val nullableUser: User? = null
val greeting = nullableUser?.run {
    "Привет, $name!"
} ?: "Пользователь не найден"
println(greeting) // Пользователь не найден

Когда использовать run:

  • Когда нужно выполнить несколько операций и вернуть результат.
  • Безопасная работа с nullable (в паре с ?.).
  • Альтернатива with, когда объект nullable

Блоги

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