Функции области видимости
Стандартная библиотека Kotlin содержит несколько функций, единственная цель которых — выполнять блок кода в контексте объекта. Когда вы вызываете такую функцию для объекта и передаёте ей лямбда-выражение, создаётся временная область видимости. В этой области видимости можно обращаться к объекту, не указывая его имя. Такие функции называются функциями области видимости. Их пять: let, run, with, apply и also.
По сути, все эти функции выполняют одно и то же действие: запускают блок кода для объекта. Различаются они тем, как этот объект становится доступен внутри блока и каков результат всего выражения.
Вот типичный пример использования функции области видимости:
data class Person(var name: String, var age: Int, var city: String) {
fun moveTo(newCity: String) { city = newCity }
fun incrementAge() { age++ }
}
fun main() {
//sampleStart
Person("Alice", 20, "Amsterdam").let {
println(it)
it.moveTo("London")
it.incrementAge()
println(it)
}
//sampleEnd
}
Если написать то же самое без let, придётся объявить новую переменную и каждый раз указывать её имя при использовании.
data class Person(var name: String, var age: Int, var city: String) {
fun moveTo(newCity: String) { city = newCity }
fun incrementAge() { age++ }
}
fun main() {
//sampleStart
val alice = Person("Alice", 20, "Amsterdam")
println(alice)
alice.moveTo("London")
alice.incrementAge()
println(alice)
//sampleEnd
}
Функции области видимости не добавляют новых технических возможностей, но могут сделать код более кратким и удобочитаемым.
Из-за большого сходства функций области видимости бывает непросто выбрать подходящую для конкретного случая. Выбор в основном зависит от ваших намерений и принятых в проекте соглашений. Ниже мы подробно описываем различия между функциями области видимости и рекомендации по их использованию.
Выбор функции
Чтобы помочь выбрать подходящую для вашей задачи функцию области видимости, мы подготовили таблицу с кратким обзором основных различий между ними.
Функция |
Ссылка на объект |
Возвращаемое значение |
Является функцией-расширением |
|---|---|---|---|
|
Результат лямбды |
Да |
|
|
Результат лямбды |
Да |
|
- |
Результат лямбды |
Нет: вызывается без объекта контекста |
|
|
Результат лямбды |
Нет: объект контекста передаётся в качестве аргумента. |
|
|
Объект контекста |
Да |
|
|
Объект контекста |
Да |
Подробная информация об этих функциях приведена в соответствующих разделах ниже.
Краткое руководство по выбору функций области видимости в зависимости от цели:
Выполнение лямбды для ненулевых объектов:
letВведение выражения в качестве переменной в локальной области видимости:
letНастройка объекта:
applyНастройка объекта и вычисление результата:
runВыполнение инструкций там, где требуется выражение: функция
run, не являющаяся функцией-расширениемДополнительные действия:
alsoГруппировка вызовов функций для объекта:
with
Области применения разных функций области видимости пересекаются, поэтому можно выбирать функции, руководствуясь соглашениями, принятыми в вашем проекте или команде.
Хотя функции области видимости помогают сделать код более кратким, не злоупотребляйте ими: это может затруднить чтение кода и привести к ошибкам. Мы также рекомендуем не вкладывать функции области видимости друг в друга и соблюдать осторожность при их объединении в цепочки: легко запутаться в том, какой объект контекста и какое значение this или it используются в данный момент.
Различия
Поскольку функции области видимости похожи по своей природе, важно понимать, чем они различаются. Между функциями области видимости есть два основных различия:
Способ обращения к объекту контекста.
Возвращаемое значение.
Объект контекста: this или it
Внутри лямбды, переданной функции области видимости, объект контекста доступен по краткой ссылке, а не по своему имени. Каждая функция области видимости использует один из двух способов обращения к объекту контекста: как к получателю лямбды (this) или как к аргументу лямбды (it). Оба способа предоставляют одинаковые возможности, поэтому мы описываем их преимущества и недостатки для разных случаев использования и даём рекомендации по их применению.
fun main() {
val str = "Hello"
// this
str.run {
println("The string's length: $length")
//println("The string's length: ${this.length}") // does the same
}
// it
str.let {
println("The string's length is ${it.length}")
}
}
this
run, with и apply ссылаются на объект контекста как на получатель лямбды — с помощью ключевого слова this. Поэтому в их лямбдах объект доступен так же, как и в обычных функциях класса.
В большинстве случаев при обращении к членам объекта-получателя можно опустить this, что делает код короче. С другой стороны, если опустить this, бывает трудно отличить члены получателя от внешних объектов или функций. Поэтому рекомендуется использовать объект контекста как получатель (this) в лямбдах, которые в основном работают с членами объекта, вызывая его функции или присваивая значения свойствам.
data class Person(var name: String, var age: Int = 0, var city: String = "")
fun main() {
//sampleStart
val adam = Person("Adam").apply {
age = 20 // same as this.age = 20
city = "London"
}
println(adam)
//sampleEnd
}
it
В свою очередь, let и also ссылаются на объект контекста как на аргумент лямбды. Если имя аргумента не указано, к объекту обращаются по неявному имени по умолчанию it. it короче, чем this, а выражения с it обычно легче читать.
Однако при вызове функций или обращении к свойствам объекта он не доступен неявно, как this. Поэтому обращаться к объекту контекста через it удобнее, если объект в основном используется в качестве аргумента вызовов функций. it также предпочтительнее, если в блоке кода используется несколько переменных.
import kotlin.random.Random
fun writeToLog(message: String) {
println("INFO: $message")
}
fun main() {
//sampleStart
fun getRandomInt(): Int {
return Random.nextInt(100).also {
writeToLog("getRandomInt() generated value $it")
}
}
val i = getRandomInt()
println(i)
//sampleEnd
}
В приведённом ниже примере объект контекста используется как аргумент лямбды с именем аргумента: value.
import kotlin.random.Random
fun writeToLog(message: String) {
println("INFO: $message")
}
fun main() {
//sampleStart
fun getRandomInt(): Int {
return Random.nextInt(100).also { value ->
writeToLog("getRandomInt() generated value $value")
}
}
val i = getRandomInt()
println(i)
//sampleEnd
}
Возвращаемое значение
Функции области видимости различаются возвращаемым результатом:
applyиalsoвозвращают объект контекста.let,runиwithвозвращают результат лямбды.
Тщательно продумайте, какое возвращаемое значение вам нужно, исходя из того, что вы собираетесь делать дальше в коде. Это поможет выбрать наиболее подходящую функцию области видимости.
Объект контекста
apply и also возвращают сам объект контекста. Поэтому их можно включать в цепочки вызовов как промежуточные шаги: продолжать вызывать функции для того же объекта одну за другой.
fun main() {
//sampleStart
val numberList = mutableListOf<Double>()
numberList.also { println("Populating the list") }
.apply {
add(2.71)
add(3.14)
add(1.0)
}
.also { println("Sorting the list") }
.sort()
//sampleEnd
println(numberList)
}
Их также можно использовать в операторах return функций, возвращающих объект контекста.
import kotlin.random.Random
fun writeToLog(message: String) {
println("INFO: $message")
}
fun main() {
//sampleStart
fun getRandomInt(): Int {
return Random.nextInt(100).also {
writeToLog("getRandomInt() generated value $it")
}
}
val i = getRandomInt()
//sampleEnd
}
Результат лямбды
let, run и with возвращают результат лямбды. Поэтому их можно использовать, чтобы присвоить результат переменной, продолжить цепочку операций с результатом и так далее.
fun main() {
//sampleStart
val numbers = mutableListOf("one", "two", "three")
val countEndsWithE = numbers.run {
add("four")
add("five")
count { it.endsWith("e") }
}
println("There are $countEndsWithE elements that end with e.")
//sampleEnd
}
Кроме того, возвращаемое значение можно игнорировать, используя функцию области видимости для создания временной области видимости для локальных переменных.
fun main() {
//sampleStart
val numbers = mutableListOf("one", "two", "three")
with(numbers) {
val firstItem = first()
val lastItem = last()
println("First item: $firstItem, last item: $lastItem")
}
//sampleEnd
}
Функции
Чтобы помочь выбрать подходящую для вашего случая функцию области видимости, мы подробно описываем их и даём рекомендации по использованию. Во многих случаях функции области видимости технически взаимозаменяемы, поэтому в примерах показаны принятые способы их применения.
let
Объект контекста доступен как аргумент (
it).Возвращаемое значение — результат лямбды.
let можно использовать для вызова одной или нескольких функций для результатов цепочек вызовов. Например, следующий код выводит результаты двух операций над коллекцией:
fun main() {
//sampleStart
val numbers = mutableListOf("one", "two", "three", "four", "five")
val resultList = numbers.map { it.length }.filter { it > 3 }
println(resultList)
//sampleEnd
}
С помощью let можно переписать приведённый выше пример так, чтобы не присваивать результат операций со списком переменной:
fun main() {
//sampleStart
val numbers = mutableListOf("one", "two", "three", "four", "five")
numbers.map { it.length }.filter { it > 3 }.let {
println(it)
// and more function calls if needed
}
//sampleEnd
}
Если блок кода, переданный в let, содержит одну функцию, принимающую it в качестве аргумента, вместо аргумента лямбды можно использовать ссылку на метод (::):
fun main() {
//sampleStart
val numbers = mutableListOf("one", "two", "three", "four", "five")
numbers.map { it.length }.filter { it > 3 }.let(::println)
//sampleEnd
}
let часто используется для выполнения блока кода с ненулевыми значениями. Чтобы выполнить действия с объектом, допускающим значение null, примените к нему оператор безопасного вызова ?. и вызовите let, передав действия в его лямбду.
fun processNonNullString(str: String) {}
fun main() {
//sampleStart
val str: String? = "Hello"
//processNonNullString(str) // compilation error: str can be null
val length = str?.let {
println("let() called on $it")
processNonNullString(it) // OK: 'it' is not null inside '?.let { }'
it.length
}
//sampleEnd
}
Можно также использовать let, чтобы вводить локальные переменные с ограниченной областью видимости и сделать код более удобным для чтения. Чтобы объявить новую переменную для объекта контекста, укажите её имя в качестве аргумента лямбды, чтобы использовать его вместо имени по умолчанию it.
fun main() {
//sampleStart
val numbers = listOf("one", "two", "three", "four")
val modifiedFirstItem = numbers.first().let { firstItem ->
println("The first item of the list is '$firstItem'")
if (firstItem.length >= 5) firstItem else "!" + firstItem + "!"
}.uppercase()
println("First item after modifications: '$modifiedFirstItem'")
//sampleEnd
}
with
Объект контекста доступен как получатель (
this).Возвращаемое значение — результат лямбды.
Поскольку with не является функцией-расширением, объект контекста передаётся в качестве аргумента, но внутри лямбды доступен как получатель (this).
Мы рекомендуем использовать with для вызова функций объекта контекста, если возвращаемый результат не нужен. В коде with можно прочитать как «с этим объектом выполни следующие действия».
fun main() {
//sampleStart
val numbers = mutableListOf("one", "two", "three")
with(numbers) {
println("'with' is called with argument $this")
println("It contains $size elements")
}
//sampleEnd
}
Можно также использовать with, чтобы ввести вспомогательный объект, свойства или функции которого используются для вычисления значения.
fun main() {
//sampleStart
val numbers = mutableListOf("one", "two", "three")
val firstAndLast = with(numbers) {
"The first element is ${first()}," +
" the last element is ${last()}"
}
println(firstAndLast)
//sampleEnd
}
run
Объект контекста доступен как получатель (
this).Возвращаемое значение — результат лямбды.
run выполняет то же, что и with, но реализована как функция-расширение. Поэтому, как и let, её можно вызвать для объекта контекста с помощью точечной нотации.
run полезна, когда лямбда одновременно инициализирует объекты и вычисляет возвращаемое значение.
class MultiportService(var url: String, var port: Int) {
fun prepareRequest(): String = "Default request"
fun query(request: String): String = "Result for query '$request'"
}
fun main() {
//sampleStart
val service = MultiportService("https://example.kotlinlang.org", 80)
val result = service.run {
port = 8080
query(prepareRequest() + " to port $port")
}
// the same code written with let() function:
val letResult = service.let {
it.port = 8080
it.query(it.prepareRequest() + " to port ${it.port}")
}
//sampleEnd
println(result)
println(letResult)
}
Можно также вызвать run как функцию, не являющуюся функцией-расширением. Вариант run, не являющийся функцией-расширением, не принимает объект контекста, но всё равно возвращает результат лямбды. run, не являющаяся функцией-расширением, позволяет выполнить блок из нескольких инструкций там, где требуется выражение. В коде run, не являющуюся функцией-расширением, можно прочитать как «выполни блок кода и вычисли результат».
fun main() {
//sampleStart
val hexNumberRegex = run {
val digits = "0-9"
val hexDigits = "A-Fa-f"
val sign = "+-"
Regex("[$sign]?[$digits$hexDigits]+")
}
for (match in hexNumberRegex.findAll("+123 -FFFF !%*& 88 XYZ")) {
println(match.value)
}
//sampleEnd
}
apply
Объект контекста доступен как получатель (
this).Возвращаемое значение — сам объект.
Поскольку apply возвращает сам объект контекста, мы рекомендуем использовать её для блоков кода, которые не возвращают значение и в основном работают с членами объекта-получателя. Чаще всего apply используется для настройки объекта. Такие вызовы можно прочитать как «примени к объекту следующие присваивания».
data class Person(var name: String, var age: Int = 0, var city: String = "")
fun main() {
//sampleStart
val adam = Person("Adam").apply {
age = 32
city = "London"
}
println(adam)
//sampleEnd
}
Ещё один вариант использования apply — включение apply в несколько цепочек вызовов для более сложной обработки.
also
Объект контекста доступен как аргумент (
it).Возвращаемое значение — сам объект.
also полезна для выполнения действий, которым объект контекста передаётся в качестве аргумента. Используйте also для действий, которым нужна ссылка на объект, а не его свойства и функции, или если вы не хотите затенять ссылку this из внешней области видимости.
Увидев в коде also, можно прочитать это как «а также выполни с объектом следующие действия».
fun main() {
//sampleStart
val numbers = mutableListOf("one", "two", "three")
numbers
.also { println("The list elements before adding new one: $it") }
.add("four")
//sampleEnd
}
takeIf и takeUnless
Помимо функций области видимости, стандартная библиотека содержит функции takeIf и takeUnless. Эти функции позволяют проверять состояние объекта в цепочках вызовов.
При вызове для объекта с предикатом takeIf возвращает этот объект, если он удовлетворяет заданному предикату. В противном случае она возвращает null. Таким образом, takeIf — это функция фильтрации отдельного объекта.
Логика takeUnless противоположна логике takeIf. При вызове для объекта с предикатом takeUnless возвращает null, если объект удовлетворяет заданному предикату. В противном случае она возвращает объект.
При использовании takeIf или takeUnless объект доступен как аргумент лямбды (it).
import kotlin.random.*
fun main() {
//sampleStart
val number = Random.nextInt(100)
val evenOrNull = number.takeIf { it % 2 == 0 }
val oddOrNull = number.takeUnless { it % 2 == 0 }
println("even: $evenOrNull, odd: $oddOrNull")
//sampleEnd
}
fun main() {
//sampleStart
val str = "Hello"
val caps = str.takeIf { it.isNotEmpty() }?.uppercase()
//val caps = str.takeIf { it.isNotEmpty() }.uppercase() //compilation error
println(caps)
//sampleEnd
}
takeIf и takeUnless особенно полезны в сочетании с функциями области видимости. Например, можно объединить takeIf и takeUnless с let, чтобы выполнить блок кода для объектов, соответствующих заданному предикату. Для этого вызовите takeIf для объекта, а затем вызовите let с безопасным вызовом (?). Для объектов, не соответствующих предикату, takeIf возвращает null, и let не вызывается.
fun main() {
//sampleStart
fun displaySubstringPosition(input: String, sub: String) {
input.indexOf(sub).takeIf { it >= 0 }?.let {
println("The substring $sub is found in $input.")
println("Its start position is $it.")
}
}
displaySubstringPosition("010000011", "11")
displaySubstringPosition("010000011", "12")
//sampleEnd
}
Для сравнения ниже показан пример реализации той же функции без использования takeIf или функций области видимости:
fun main() {
//sampleStart
fun displaySubstringPosition(input: String, sub: String) {
val index = input.indexOf(sub)
if (index >= 0) {
println("The substring $sub is found in $input.")
println("Its start position is $index.")
}
}
displaySubstringPosition("010000011", "11")
displaySubstringPosition("010000011", "12")
//sampleEnd
}
© 2010–2026 JetBrains s.r.o. and Kotlin Programming Language contributors
Licensed under the Apache License, Version 2.0.
https://kotlinlang.org/docs/scope-functions.html