Spec-Zone.ru › Kotlin 2

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

В этой главе вы углубите свои знания функций расширения и узнаете, как использовать функции области видимости, чтобы писать более идиоматичный код.

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

В программировании область видимости — это область, в которой распознаётся ваша переменная или объект. Чаще всего говорят о глобальной и локальной областях видимости:

  • Глобальная область видимости — переменная или объект, доступные из любой части программы.

  • Локальная область видимости — переменная или объект, доступные только внутри блока или функции, в которой они определены.

В Kotlin также есть функции области видимости, которые позволяют создать временную область видимости вокруг объекта и выполнить некоторый код.

Функции области видимости делают код более лаконичным, поскольку внутри временной области видимости вам не нужно обращаться к объекту по имени. В зависимости от функции области видимости вы можете получить доступ к объекту, указав его с помощью ключевого слова this или используя его как аргумент с помощью ключевого слова it.

Всего в Kotlin пять функций области видимости: let, apply, run, also и with.

Каждая функция области видимости принимает лямбда-выражение и возвращает либо объект, либо результат лямбда-выражения. В этом туре мы расскажем о каждой функции области видимости и о том, как её использовать.

Вы также можете посмотреть доклад Себастьяна Айгнера, представителя Kotlin для разработчиков, о функциях области видимости: Back to the Stdlib: Making the Most of Kotlin's Standard Library.

Let

Используйте функцию области видимости let, если хотите проверить значения на null в коде, а затем выполнить дополнительные действия с возвращённым объектом.

Рассмотрим пример:

fun sendNotification(recipientAddress: String): String {
    println("Yo $recipientAddress!")
    return "Notification sent!"
}

fun getNextAddress(): String {
    return "sebastian@jetbrains.com"
}

fun main() {
    val address: String? = getNextAddress()
    sendNotification(address)
}

В примере есть две функции:

  • sendNotification(), которая принимает параметр-функцию recipientAddress и возвращает строку.

  • getNextAddress(), которая не принимает параметров и возвращает строку.

В примере создаётся переменная address, имеющая nullable-тип String. Но это становится проблемой при вызове функции sendNotification(), поскольку эта функция не предполагает, что address может иметь значение null. В результате компилятор сообщает об ошибке:

Argument type mismatch: actual type is 'String?', but 'String' was expected.

Из тура для начинающих вы уже знаете, что можно проверить значение на null с помощью условия if или использовать оператор Элвиса ?:. Но что, если вы хотите использовать возвращённый объект позже в коде? Этого можно добиться с помощью условия if и ветви else:

fun sendNotification(recipientAddress: String): String {
    println("Yo $recipientAddress!")
    return "Notification sent!"
}

fun getNextAddress(): String {
    return "sebastian@jetbrains.com"
}

fun main() { 
    //sampleStart
    val address: String? = getNextAddress()
    val confirm = if(address != null) {
        sendNotification(address)
    } else { null }
    //sampleEnd
}

Однако более лаконичный способ — использовать функцию области видимости let:

fun sendNotification(recipientAddress: String): String {
    println("Yo $recipientAddress!")
    return "Notification sent!"
}

fun getNextAddress(): String {
    return "sebastian@jetbrains.com"
}

fun main() {
    //sampleStart
    val address: String? = getNextAddress()
    val confirm = address?.let {
        sendNotification(it)
    }
    //sampleEnd
}

В примере:

  • Создаются переменные address и confirm.

  • Для переменной address используется безопасный вызов функции области видимости let.

  • Внутри функции области видимости let создаётся временная область видимости.

  • Функция sendNotification() передаётся в функцию области видимости let как лямбда-выражение.

  • Временная область видимости используется для обращения к переменной address через it.

  • Результат присваивается переменной confirm.

Такой подход позволяет коду обрабатывать ситуацию, когда переменная address может иметь значение null, и использовать переменную confirm позже в коде.

Apply

Используйте функцию области видимости apply для инициализации объектов, например экземпляра класса, во время их создания, а не позже в коде. Такой подход упрощает чтение и поддержку кода.

Рассмотрим пример:

class Client() {
    var token: String? = null
    fun connect() = println("connected!")
    fun authenticate() = println("authenticated!")
    fun getData() : String {
        println("getting data!")
        return "Mock data"
    }
}

val client = Client()

fun main() {
    client.token = "asdf"
    client.connect()
    // connected!
    client.authenticate()
    // authenticated!
    client.getData()
    // getting data!
}

В примере есть класс Client, содержащий одно свойство token и три функции-члена: connect(), authenticate() и getData().

В примере переменная client создаётся как экземпляр класса Client, после чего в функции main() инициализируется свойство token и вызываются функции-члены.

Хотя этот пример компактен, в реальной ситуации после создания экземпляра класса может пройти некоторое время, прежде чем вы сможете настроить его и использовать (вместе с его функциями-членами). Однако, если использовать функцию области видимости apply, можно создать экземпляр класса, настроить его и вызвать его функции-члены в одном месте кода:

class Client() {
    var token: String? = null
    fun connect() = println("connected!")
    fun authenticate() = println("authenticated!")
    fun getData() : String {
        println("getting data!")
        return "Mock data"
    }
}
//sampleStart
val client = Client().apply {
    token = "asdf"
    connect()
    // connected!
    authenticate()
    // authenticated!
}

fun main() {
    client.getData()
    // getting data!
}
//sampleEnd

В примере:

  • Переменная client создаётся как экземпляр класса Client.

  • Для экземпляра client используется функция области видимости apply.

  • Внутри функции области видимости apply создаётся временная область видимости, поэтому при обращении к свойствам и функциям не нужно явно указывать экземпляр client.

  • В функцию области видимости apply передаётся лямбда-выражение, которое обновляет свойство token и вызывает функции connect() и authenticate().

  • В функции main() для экземпляра client вызывается функция-член getData().

Как видите, этот подход удобен при работе с большими фрагментами кода.

Run

Как и apply, функцию области видимости run можно использовать для инициализации объекта, но run лучше подходит для инициализации объекта в определённый момент кода и немедленного вычисления результата.

Продолжим предыдущий пример с функцией apply, но теперь сгруппируем функции connect() и authenticate(), чтобы они вызывались при каждом запросе.

Например:

class Client() {
    var token: String? = null
    fun connect() = println("connected!")
    fun authenticate() = println("authenticated!")
    fun getData() : String {
        println("getting data!")
        return "Mock data"
    }
}

//sampleStart
val client: Client = Client().apply {
    token = "asdf"
}

fun main() {
    val result: String = client.run {
        connect()
        // connected!
        authenticate()
        // authenticated!
        getData()
        // getting data!
    }
}
//sampleEnd

В примере:

  • Переменная client создаётся как экземпляр класса Client.

  • Для экземпляра client используется функция области видимости apply.

  • Внутри функции области видимости apply создаётся временная область видимости, поэтому при обращении к свойствам и функциям не нужно явно указывать экземпляр client.

  • В функцию области видимости apply передаётся лямбда-выражение, обновляющее свойство token.

Функция main():

  • Создаёт переменную result типа String.

  • Для экземпляра client используется функция области видимости run.

  • Внутри функции области видимости run создаётся временная область видимости, поэтому при обращении к свойствам и функциям не нужно явно указывать экземпляр client.

  • В функцию области видимости run передаётся лямбда-выражение, вызывающее функции connect(), authenticate() и getData().

  • Результат присваивается переменной result.

Теперь вы можете использовать возвращённый результат дальше в коде.

Also

Используйте функцию области видимости also, чтобы выполнить дополнительное действие с объектом, например записать журнал, а затем вернуть объект для дальнейшего использования в коде.

Рассмотрим пример:

fun main() {
    val medals: List<String> = listOf("Gold", "Silver", "Bronze")
    val reversedLongUppercaseMedals: List<String> =
        medals
            .map { it.uppercase() }
            .filter { it.length > 4 }
            .reversed()
    println(reversedLongUppercaseMedals)
    // [BRONZE, SILVER]
}

В примере:

  • Создаётся переменная medals, содержащая список строк.

  • Создаётся переменная reversedLongUpperCaseMedals типа List<String>.

  • Для переменной medals используется функция расширения .map().

  • В функцию .map() передаётся лямбда-выражение, которое обращается к medals через ключевое слово it и вызывает для него функцию расширения .uppercase().

  • Для переменной medals используется функция расширения .filter().

  • В функцию .filter() в качестве предиката передаётся лямбда-выражение, которое обращается к medals через ключевое слово it и проверяет, содержит ли элемент списка более 4 символов.

  • Для переменной medals используется функция расширения .reversed().

  • Результат присваивается переменной reversedLongUpperCaseMedals.

  • Выводится список, содержащийся в переменной reversedLongUpperCaseMedals.

Было бы полезно добавить журналирование между вызовами функций, чтобы увидеть, что происходит с переменной medals. Для этого подходит функция also:

fun main() {
    val medals: List<String> = listOf("Gold", "Silver", "Bronze")
    val reversedLongUppercaseMedals: List<String> =
        medals
            .map { it.uppercase() }
            .also { println(it) }
            // [GOLD, SILVER, BRONZE]
            .filter { it.length > 4 }
            .also { println(it) }
            // [SILVER, BRONZE]
            .reversed()
    println(reversedLongUppercaseMedals)
    // [BRONZE, SILVER]
}

Теперь в примере:

  • Для переменной medals используется функция области видимости also.

  • Внутри функции области видимости also создаётся временная область видимости, поэтому при использовании переменной medals в качестве параметра функции не нужно указывать её явно.

  • В функцию области видимости also передаётся лямбда-выражение, вызывающее функцию println() с переменной medals в качестве параметра функции через ключевое слово it.

Поскольку функция also возвращает объект, она полезна не только для журналирования, но и для отладки, последовательного вызова нескольких операций и выполнения других побочных операций, не влияющих на основной ход выполнения кода.

With

В отличие от остальных функций области видимости, with не является функцией расширения, поэтому синтаксис отличается. Передайте объект-получатель функции with в качестве аргумента.

Используйте функцию области видимости with, если хотите вызвать несколько функций для одного объекта.

Рассмотрим пример:

class Canvas {
    fun rect(x: Int, y: Int, w: Int, h: Int): Unit = println("$x, $y, $w, $h")
    fun circ(x: Int, y: Int, rad: Int): Unit = println("$x, $y, $rad")
    fun text(x: Int, y: Int, str: String): Unit = println("$x, $y, $str")
}

fun main() {
    val mainMonitorPrimaryBufferBackedCanvas = Canvas()

    mainMonitorPrimaryBufferBackedCanvas.text(10, 10, "Foo")
    mainMonitorPrimaryBufferBackedCanvas.rect(20, 30, 100, 50)
    mainMonitorPrimaryBufferBackedCanvas.circ(40, 60, 25)
    mainMonitorPrimaryBufferBackedCanvas.text(15, 45, "Hello")
    mainMonitorPrimaryBufferBackedCanvas.rect(70, 80, 150, 100)
    mainMonitorPrimaryBufferBackedCanvas.circ(90, 110, 40)
    mainMonitorPrimaryBufferBackedCanvas.text(35, 55, "World")
    mainMonitorPrimaryBufferBackedCanvas.rect(120, 140, 200, 75)
    mainMonitorPrimaryBufferBackedCanvas.circ(160, 180, 55)
    mainMonitorPrimaryBufferBackedCanvas.text(50, 70, "Kotlin")
}

В примере создаётся класс Canvas с тремя функциями-членами: rect(), circ() и text(). Каждая из этих функций-членов выводит инструкцию, сформированную из переданных вами параметров функции.

В примере переменная mainMonitorPrimaryBufferBackedCanvas создаётся как экземпляр класса Canvas, после чего для этого экземпляра вызывается последовательность функций-членов с разными параметрами.

Как видите, этот код трудно читать. Если использовать функцию with, код станет проще:

class Canvas {
    fun rect(x: Int, y: Int, w: Int, h: Int): Unit = println("$x, $y, $w, $h")
    fun circ(x: Int, y: Int, rad: Int): Unit = println("$x, $y, $rad")
    fun text(x: Int, y: Int, str: String): Unit = println("$x, $y, $str")
}

fun main() {
    //sampleStart
    val mainMonitorSecondaryBufferBackedCanvas = Canvas()
    with(mainMonitorSecondaryBufferBackedCanvas) {
        text(10, 10, "Foo")
        rect(20, 30, 100, 50)
        circ(40, 60, 25)
        text(15, 45, "Hello")
        rect(70, 80, 150, 100)
        circ(90, 110, 40)
        text(35, 55, "World")
        rect(120, 140, 200, 75)
        circ(160, 180, 55)
        text(50, 70, "Kotlin")
    }
    //sampleEnd
}

В этом примере:

  • Функция области видимости with используется с экземпляром mainMonitorSecondaryBufferBackedCanvas в качестве получателя.

  • Внутри функции области видимости with создаётся временная область видимости, поэтому при вызове функций-членов не нужно явно указывать экземпляр mainMonitorSecondaryBufferBackedCanvas.

  • В функцию области видимости with передаётся лямбда-выражение, вызывающее последовательность функций-членов с разными параметрами.

Теперь этот код намного проще читать, и вероятность ошибок ниже.

Обзор вариантов использования

В этом разделе мы рассмотрели различные функции области видимости, доступные в Kotlin, и основные способы их применения для написания более идиоматичного кода. Эту таблицу можно использовать в качестве краткой справки. Обратите внимание, что для использования этих функций в коде не обязательно полностью понимать принцип их работы.

Функция

Доступ к x через

Возвращаемое значение

Вариант использования

let

it

Результат лямбда-выражения

Проверить значения на null в коде, а затем выполнить дополнительные действия с возвращённым объектом.

apply

this

x

Инициализировать объекты во время их создания.

run

this

Результат лямбда-выражения

Инициализировать объекты во время их создания И вычислить результат.

also

it

x

Выполнить дополнительные действия перед возвратом объекта.

with

this

Результат лямбда-выражения

Вызвать несколько функций для одного объекта.

Дополнительные сведения о функциях области видимости см. в разделе Функции области видимости.

Практика

Упражнение 1

Перепишите функцию .getPriceInEuros() в виде функции с единственным выражением, используя операторы безопасного вызова ?. и функцию области видимости let.

Подсказка

Используйте операторы безопасного вызова ?., чтобы безопасно обратиться к свойству priceInDollars функции getProductInfo(). Затем используйте функцию области видимости let, чтобы перевести значение priceInDollars в евро.

data class ProductInfo(val priceInDollars: Double?)

class Product {
    fun getProductInfo(): ProductInfo? {
        return ProductInfo(100.0)
    }
}

// Rewrite this function
fun Product.getPriceInEuros(): Double? {
    val info = getProductInfo()
    if (info == null) return null
    val price = info.priceInDollars
    if (price == null) return null
    return convertToEuros(price)
}

fun convertToEuros(dollars: Double): Double {
    return dollars * 0.85
}

fun main() {
    val product = Product()
    val priceInEuros = product.getPriceInEuros()

    if (priceInEuros != null) {
        println("Price in Euros: €$priceInEuros")
        // Price in Euros: €85.0
    } else {
        println("Price information is not available.")
    }
}
data class ProductInfo(val priceInDollars: Double?) class Product { fun getProductInfo(): ProductInfo? { return ProductInfo(100.0) } } fun Product.getPriceInEuros() = getProductInfo()?.priceInDollars?.let { convertToEuros(it) } fun convertToEuros(dollars: Double): Double { return dollars * 0.85 } fun main() { val product = Product() val priceInEuros = product.getPriceInEuros() if (priceInEuros != null) { println("Price in Euros: €$priceInEuros") // Price in Euros: €85.0 } else { println("Price information is not available.") } }

Упражнение 2

Есть функция updateEmail(), которая обновляет адрес электронной почты пользователя. Используйте функцию области видимости apply, чтобы обновить адрес электронной почты, а затем функцию области видимости also, чтобы вывести сообщение в журнал: Updating email for user with ID: ${it.id}.

data class User(val id: Int, var email: String)

fun updateEmail(user: User, newEmail: String): User = // Write your code here

fun main() {
    val user = User(1, "old_email@example.com")
    val updatedUser = updateEmail(user, "new_email@example.com")
    // Updating email for user with ID: 1

    println("Updated User: $updatedUser")
    // Updated User: User(id=1, email=new_email@example.com)
}
data class User(val id: Int, var email: String) fun updateEmail(user: User, newEmail: String): User = user.apply { this.email = newEmail }.also { println("Updating email for user with ID: ${it.id}") } fun main() { val user = User(1, "old_email@example.com") val updatedUser = updateEmail(user, "new_email@example.com") // Updating email for user with ID: 1 println("Updated User: $updatedUser") // Updated User: User(id=1, email=new_email@example.com) }
  • Предыдущий шаг

  • Следующий шаг

12 августа 2026 г.
Функции расширенияЛямбда-выражения с получателем

© 2010–2026 JetBrains s.r.o. and Kotlin Programming Language contributors
Licensed under the Apache License, Version 2.0.
https://kotlinlang.org/docs/kotlin-tour-intermediate-scope-functions.html

Spec-Zone.ru

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