Функции области видимости
В этой главе вы углубите свои знания функций расширения и узнаете, как использовать функции области видимости, чтобы писать более идиоматичный код.
Функции области видимости
В программировании область видимости — это область, в которой распознаётся ваша переменная или объект. Чаще всего говорят о глобальной и локальной областях видимости:
Глобальная область видимости — переменная или объект, доступные из любой части программы.
Локальная область видимости — переменная или объект, доступные только внутри блока или функции, в которой они определены.
В Kotlin также есть функции области видимости, которые позволяют создать временную область видимости вокруг объекта и выполнить некоторый код.
Функции области видимости делают код более лаконичным, поскольку внутри временной области видимости вам не нужно обращаться к объекту по имени. В зависимости от функции области видимости вы можете получить доступ к объекту, указав его с помощью ключевого слова this или используя его как аргумент с помощью ключевого слова it.
Всего в Kotlin пять функций области видимости: let, apply, run, also и with.
Каждая функция области видимости принимает лямбда-выражение и возвращает либо объект, либо результат лямбда-выражения. В этом туре мы расскажем о каждой функции области видимости и о том, как её использовать.
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, и основные способы их применения для написания более идиоматичного кода. Эту таблицу можно использовать в качестве краткой справки. Обратите внимание, что для использования этих функций в коде не обязательно полностью понимать принцип их работы.
Функция |
Доступ к |
Возвращаемое значение |
Вариант использования |
|---|---|---|---|
|
|
Результат лямбда-выражения |
Проверить значения на null в коде, а затем выполнить дополнительные действия с возвращённым объектом. |
|
|
|
Инициализировать объекты во время их создания. |
|
|
Результат лямбда-выражения |
Инициализировать объекты во время их создания И вычислить результат. |
|
|
|
Выполнить дополнительные действия перед возвратом объекта. |
|
|
Результат лямбда-выражения |
Вызвать несколько функций для одного объекта. |
Дополнительные сведения о функциях области видимости см. в разделе Функции области видимости.
Практика
Упражнение 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.")
}
}
Упражнение 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)
}
© 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