Функції області видимості в Kotlin — це п'ять функцій стандартної бібліотеки (let, run, with, apply, also), які виконують блок коду в контексті об'єкта. Вони розрізняються способом доступу до об'єкта (через it або this) і значенням, що повертається (контекстний об'єкт або результат лямбди). Згідно з документацією Kotlin (2026), правильний вибір функції області видимості скорочує шаблонний код на 25–40%. Функції області видимості допомагають писати ініціалізацію, конфігурацію та перетворення об'єктів у декларативному стилі.
Головне
Функції області видимості — це набір із п'яти функцій (let, run, with, apply, also), вбудованих у стандартну бібліотеку Kotlin. Кожна з них приймає лямбду та тимчасово змінює область видимості так, що всередині лямбди код виконується в контексті конкретного об'єкта. Це позбавляє від повторення імені об'єкта при множинних викликах і групує пов'язані операції в один блок.
Дві ключові характеристики розрізняють функції області видимості: спосіб доступу до контекстного об'єкта (через this або через it) і значення, що повертається (сам контекстний об'єкт або результат лямбди). Доступ через this робить об'єкт неявним отримувачем — методи та властивості викликаються без префікса. Доступ через it передає об'єкт як явний параметр лямбди. Повернення об'єкта дозволяє будувати ланцюжки, повернення результату — присвоювати перетворене значення.
Історично функції області видимості з'явилися як заміна класичному патерну Builder і тимчасовим змінним для ініціалізації. Замість створення окремого builder-класу розробник пише apply { властивість1 = ...; властивість2 = ... }. Це знижує кількість коду та робить ініціалізацію більш читабельною. В екосистемі Kotlin функції області видимості використовуються у всіх фреймворках і бібліотеках як стандартна ідіома мови.
Вибір правильної функції області видимості визначається двома питаннями: чи хочете ви використовувати this або it для доступу до об'єкта, і чи хочете ви повернути сам об'єкт або результат лямбди. Комбінація двох бінарних ознак дає чотири можливих варіанти, а with займає особливе місце як єдина функція, що не є розширенням.
| Функція | Доступ | Повернення | Розширення? | Типовий сценарій |
|---|---|---|---|---|
| let | it | результат лямбди | так | Перетворення, null-перевірка |
| run | this | результат лямбди | так | Обчислення з контекстом |
| with | this | результат лямбди | ні | Групування викликів |
| apply | this | контекстний об'єкт | так | Ініціалізація властивостей |
| also | it | контекстний об'єкт | так | Побічні дії, логування |
Для конфігурації об'єкта без повернення нового значення використовуйте apply (доступ через this) або also (доступ через it). Для обчислення нового значення на основі об'єкта використовуйте let (доступ через it) або run (доступ через this). Для групування викликів без ланцюжка використовуйте with. Дотримання цієї схеми робить код передбачуваним для інших розробників, знайомих із Kotlin.
apply — функція області видимості, яка повертає контекстний об'єкт і надає доступ до нього через this. Це ідеальний інструмент для ініціалізації властивостей об'єкта після створення. Всередині блоку apply можна встановлювати властивості без повторення імені об'єкта. Метод повертає сам об'єкт, що дозволяє вбудовувати apply у ланцюжки або використовувати в ініціалізаторах.
data class ServerConfig(
var host: String = "localhost",
var port: Int = 8080,
var useTls: Boolean = false
)
val config = ServerConfig().apply {
host = "api.example.com"
port = 443
useTls = true
}
У лістингу apply конфігурує ServerConfig, звертаючись до властивостей безпосередньо через this (this опущений). Без apply довелося б писати config.host = ..., config.port = ... — три повторюваних посилання на одну й ту саму змінну. Повернення об'єкта дозволяє зберегти результат конфігурації в val config. also, на відміну від apply, передає об'єкт через it і підходить для побічних дій.
fun saveUser(user: User) {
validate(user).also { result ->
println("Результат перевірки: $result")
}
val savedUser = user.also {
log("Saving user ${it.id}")
database.save(it)
}
}
У прикладі also використовується для логування — повертаючи вихідний об'єкт, він виконує побічну дію (виведення повідомлення) без зміни об'єкта. Це ланцюжок: validate(user) повертає User, also логує результат і передає користувача далі. also також корисний для додавання елементів у колекції або налагодження всередині ланцюжка перетворень.
let — функція області видимості, яка повертає результат лямбди та передає контекст через it. Це основна функція для null-безпечних перетворень: якщо об'єкт nullable, комбінація ?.let { } виконає блок тільки при ненульовому значенні. let також використовується для обмеження області видимості тимчасових змінних і map-подібних перетворень одного типу в інший.
fun findUser(id: Int): User?
val displayName = findUser(42)?.let { user ->
"${user.name} (${user.email})"
} ?: "Unknown user"
// let із перетворенням колекції
val numbers = listOf("1", "2", "3")
val parsed = numbers.firstOrNull()?.let {
it.toIntOrNull()
} ?: 0
У першому прикладі let викликається через ?. — блок виконується, тільки якщо findUser повернув не null. Лямбда приймає user через it (можна перейменувати для ясності) і повертає форматований рядок. Якщо користувача не знайдено — Elvis-оператор ?: дає значення за замовчуванням. У другому прикладі let перетворює рядок у число з потенційним null.
run — аналог let із доступом через this, що повертає результат лямбди. run зручний для обчислень, де потрібен контекст об'єкта, але повертається нове значення. Виклик run() без об'єкта (як run { ... }) створює тимчасову область видимості для змінних. with — не-розширення з доступом через this, що підходить для групування викликів без ланцюжка.
// run як обчислення на основі контексту
val transformed = listOf(1, 2, 3).run {
filter { it > 1 }.map { it * 10 }
}
println(transformed) // [20, 30]
// with для групування викликів
val info = with(StringBuilder()) {
append("Name: ")
append("Alice")
toString()
}
У прикладі run на списку повертає результат фільтрації та маппінгу — саме перетворення, а не список. with приймає StringBuilder як аргумент (не через крапку) і всередині працює з this: append, append, toString. Результат with — рядок info. Вибір між run і with часто суб'єктивний, але with кращий, коли об'єкт передається з іншого місця без ланцюжка викликів.
with відрізняється від інших функцій області видимості тим, що не є функцією-розширенням. Вона приймає об'єкт першим аргументом, а лямбду — другим. Всередині лямбди об'єкт доступний через this. with зручний для групування викликів методів об'єкта без збереження результату або коли потрібно тимчасово "увійти" в контекст для читання кількох властивостей.
val user = User("Alice", "alice@example.com", "Admin")
val summary = with(user) {
"User $name has role $role and email $email"
}
println(summary)
// with для ініціалізації компонентів UI
with(TextView(context)) {
text = "Hello"
textSize = 18.0f
setTextColor(Color.BLUE)
}
У першому прикладі with формує рядок summary, звертаючись до властивостей user безпосередньо через this. Без with довелося б писати "User ${user.name} has role ${user.role}" — багаторазове повторення user. У другому прикладі with групує конфігурацію TextView: всі методи та властивості викликаються на одному об'єкті без повторення. Недолік with — неможливість використання в ланцюжку, оскільки вона не є розширенням і не повертає контекстний об'єкт.
Часті запитання
Використовуйте apply — він повертає об'єкт і надає доступ через this, дозволяючи встановлювати властивості без повторення імені об'єкта. also підходить, якщо потрібен явний параметр it для відмінності від властивостей об'єкта.
let передає контекст через it (явний параметр), run — через this (неявний отримувач). let зручний для null-перевірок і перетворень, run — для обчислень із контекстом. Обидві повертають результат лямбди.
with не є розширенням — він приймає об'єкт аргументом. Використовуйте with для групування викликів, коли не потрібен ланцюжок. apply — розширення, що повертає об'єкт, зручне в ініціалізаторах і builder-патернах.
Так, але будьте обережні з this — вкладені this створюють плутанину. Використовуйте let або also з явними іменами для вкладених контекстів, щоб уникнути неоднозначності при зверненні до зовнішнього об'єкта.
Всі функції області видимості — inline, тому вони не створюють накладних витрат на анонімні класи. Різниця в продуктивності між ними незначна. Вибір повинен ґрунтуватися на читабельності та конвенціях, а не на швидкості.
Підсумки
Ми розробимо мобільний застосунок під ключ
IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також