Spec-Zone.ru › Kotlin 2

Функции области видимости

Стандартная библиотека 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

it

Результат лямбды

Да

run

this

Результат лямбды

Да

run

-

Результат лямбды

Нет: вызывается без объекта контекста

with

this

Результат лямбды

Нет: объект контекста передаётся в качестве аргумента.

apply

this

Объект контекста

Да

also

it

Объект контекста

Да

Подробная информация об этих функциях приведена в соответствующих разделах ниже.

Краткое руководство по выбору функций области видимости в зависимости от цели:

  • Выполнение лямбды для ненулевых объектов: 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
}

При добавлении других функций в цепочку после takeIf и takeUnless не забудьте проверить значение на null или использовать безопасный вызов (?.), поскольку они возвращают nullable-значение.

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
}
12 августа 2026 г.
Требования для opt-inИзмерение времени

© 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

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API