Когда использовать run, let, apply, also и with в Kotlin? Примеры и объяснение
Краткая шпаргалка: как выбирать функцию области видимости
Все пять функций (let, apply, also, run, with) выполняют блок кода в контексте объекта. Главное различие — как они возвращают результат и как обращаются к объекту внутри блока. Вот ключевые правила:
- let — возвращает результат лямбды, объект доступен как
it. Используйте для цепочек вызовов и проверок на null. - apply — возвращает сам объект, внутри блока
this. Используйте для конфигурации объекта (Builder-паттерн). - also — возвращает сам объект, внутри блока
it. Используйте для побочных действий (логирование, валидация). - run — возвращает результат лямбды, внутри блока
this. Используйте для вычислений на основе объекта. - with — возвращает результат лямбды, внутри блока
this. Используйте для группировки операций над объектом без возврата самого объекта.
Подробный разбор с примерами
1. let — для non-null проверок и цепочек
val name: String? = "Alice"
// Без let пришлось бы писать if (name != null) ...
name?.let {
println("Имя: $it") // it — это name (не null)
it.length // возвращаем длину
} ?: println("Имя null")
Когда использовать: когда нужно выполнить код только если объект не null, или когда нужно преобразовать объект в цепочке вызовов (например, data.map { ... }.firstOrNull()?.let { ... }).
2. apply — для конфигурации объекта
data class Person(var name: String = "", var age: Int = 0)
val person = Person().apply {
name = "John" // this.name = "John"
age = 30 // this.age = 30
}
// person возвращается автоматически
Когда использовать: для инициализации свойств объекта без необходимости писать person.name = ... каждый раз. Идеально подходит для Java-стиля Builder или настройки View в Android.
3. also — для побочных эффектов
val numbers = mutableListOf(1, 2, 3)
numbers.also {
println("Текущий список: $it") // логируем
}.add(4) // продолжаем цепочку
Когда использовать: когда нужно выполнить действие, не влияющее на основной поток (логирование, проверка, запись в кэш). В отличие от apply, вы не конфигурируете объект, а просто "замечаете" его.
4. run — для вычислений с контекстом this
val result = "Hello".run {
// this — это "Hello"
println("Длина: $length") // this.length
length > 3 // возвращаем Boolean
}
println(result) // true
Когда использовать: когда нужно выполнить несколько операций над объектом и вернуть какой-то результат (не сам объект). run также удобен для проверки null: nullableObject?.run { ... }.
5. with — для группировки операций (без возврата объекта)
val sb = StringBuilder()
val result = with(sb) {
append("Hello, ")
append("world!")
toString() // возвращаем результат
}
println(result) // Hello, world!
Когда использовать: когда нужно выполнить несколько действий над объектом, но сам объект вам не нужен — только результат. Обратите внимание: with — не extension-функция, а обычная функция высшего порядка.
Сравнительная таблица
Функция | Доступ к объекту | Возвращаемое значение
--------|------------------|---------------------
let | it | Результат лямбды
apply | this | Сам объект
also | it | Сам объект
run | this | Результат лямбды
with | this | Результат лямбды
Практический совет
Начните с двух правил:
- Если нужно сконфигурировать объект и вернуть его —
apply. - Если нужно проверить на null или преобразовать —
let. - Для всего остального используйте
runилиwith.
Со временем вы привыкнете к этим функциям и начнёте использовать их интуитивно.
<<Альтернативный взгляд: когда НЕ использовать scope functions
Хотя функции области видимости — мощный инструмент, новички часто злоупотребляют ими. Вот когда от них лучше отказаться:
1. Если код становится менее читаемым
// Плохо: вложенные scope functions
user?.let { u ->
u.address?.let { a ->
a.city?.let { c ->
println(c)
}
}
}
// Лучше: использовать safe call + let один раз
user?.address?.city?.let { println(it) }
2. Если вы используете apply только ради синтаксического сахара
// Избыточно
val list = mutableListOf(1, 2, 3).apply {
add(4)
}
// Проще
val list = mutableListOf(1, 2, 3)
list.add(4)
3. С with — осторожно в Android
В Android-разработке with(view) { ... } может привести к конфликту имён, если у вас есть свойства с теми же именами, что и у View. Используйте apply или явные вызовы.
Мой личный чек-лист
- let — только для non-null проверок и цепочек (не для простых вызовов).
- apply — только для инициализации объектов (не для вызова методов).
- also — только для логирования/отладки (не для изменения состояния).
- run — для вычислений, когда нужен результат.
- with — для группировки операций, но лучше заменить на run, если можно.
Помните: Kotlin — это не соревнование, кто использует больше scope functions. Код должен быть понятным в первую очередь.