Зачем нужен let, also, apply, run, with — и в чём разница между ними?
В Kotlin функции let, also, apply, run, with — это scope functions (функции области видимости), которые помогают писать более компактный, читаемый и выразительный код. Все они позволяют выполнить блок кода с объектом, не обязательно повторяя имя объекта. Однако каждая из них отличается контекстом (this или it) и значением, которое возвращается из лямбды.
let
Используется, когда нужно выполнить операцию с объектом (например, проверить его на null или трансформировать), не изменяя его.
-
Контекст: it
-
Возвращает: результат последнего выражения в лямбде
val name: String? = "Kotlin"
val length = name?.let {
println("Длина строки: ${it.length}")
it.length
}
Часто используется в сочетании с ?. для безопасного вызова:
user?.let { println("User name: ${it.name}") }
also
Применяется, когда нужно выполнить побочные действия (например, логгирование или проверку), не изменяя сам объект.
- Контекст: it
- Возвращает: сам объект (как this), на котором был вызван also
val list = mutableListOf("A", "B", "C")
.also { println("Начальный список: $it") }
Используется, когда важно сохранить цепочку вызовов, но внутри блока выполнить доп. действия:
val person = Person("Alex").also { println("Создан: ${it.name}") }
apply
Применяется, когда нужно инициализировать объект. Позволяет обращаться к членам объекта напрямую через this.
-
Контекст: this
-
Возвращает: сам объект
val paint = Paint().apply {
color = Color.RED
strokeWidth = 10f
}
Часто используется при конфигурации или создании объектов:
val textView = TextView(context).apply {
text = "Hello"
textSize = 20f
setTextColor(Color.BLACK)
}
run
Сочетает поведение let и apply: выполняет действия над объектом и возвращает результат вычислений.
- Контекст: this
- Возвращает: результат лямбды
val length = "Kotlin".run {
println("Обрабатываем строку: $this")
length
}
Также может вызываться как обычная функция:
val result = run {
val a = 5
val b = 10
a + b
}
Используется, когда нужно работать с объектом и вернуть результат, а также удобно при локальном scope.
with
Функция, а не метод объекта. Применяется для группировки операций над объектом, когда нет необходимости возвращать сам объект.
- Контекст: this
- Возвращает: результат лямбды
val result = with(StringBuilder()) {
append("Hello, ")
append("World!")
toString()
}
Синтаксически похоже на run, но вызов происходит через with(obj) { ... }, а не obj.run { ... }.
Таблица сравнения
Функция | Контекст | Возвращает | Назначение |
---|---|---|---|
let | it | Результат лямбды | Трансформация, работа с nullable |
--- | --- | --- | --- |
also | it | Сам объект | Побочные действия (лог, проверка) |
--- | --- | --- | --- |
apply | this | Сам объект | Инициализация и настройка объекта |
--- | --- | --- | --- |
run | this | Результат лямбды | Выполнение операций и получение результата |
--- | --- | --- | --- |
with | this | Результат лямбды | Работа с объектом без расширения его API |
--- | --- | --- | --- |
Примеры для сравнения
// let
val email = "test@example.com"
email.let {
println("Email длина: ${it.length}")
}
// also
val list = mutableListOf("A", "B").also {
println("Список перед добавлением: $it")
it.add("C")
}
// apply
val config = AppConfig().apply {
host = "localhost"
port = 8080
}
// run
val messageLength = "Hello".run {
println("Внутри run: $this")
length
}
// with
val result = with(StringBuilder()) {
append("Start")
append(" + Middle")
append(" + End")
toString()
}
Каждая из этих функций помогает делать код чище и выразительнее, если использовать их осознанно в зависимости от задачи: нужно ли получить объект, результат выражения или просто выполнить побочные действия.