Лямбда-выражения с получателем
В этой главе вы узнаете, как использовать получатели с другим типом функций — лямбда-выражениями — и как они помогают создавать предметно-ориентированный язык.
Лямбда-выражения с получателем
В начальном туре вы узнали, как использовать лямбда-выражения. У лямбда-выражений также может быть получатель. В этом случае лямбда-выражения могут обращаться к любым функциям-членам или свойствам получателя, не указывая его явно каждый раз. Без этих дополнительных ссылок код легче читать и поддерживать.
Синтаксис лямбда-выражения с получателем отличается при определении типа функции. Сначала укажите получатель, который хотите расширить. Затем поставьте . и завершите определение типа функции. Например:
MutableList<Int>.() -> Unit
У этого типа функции есть:
MutableList<Int>в качестве получателя.Нет параметров функции в круглых скобках
().Нет возвращаемого значения:
Unit.
Рассмотрим пример, в котором на холсте рисуются фигуры:
class Canvas {
fun drawCircle() = println("🟠 Drawing a circle")
fun drawSquare() = println("🟥 Drawing a square")
}
// Lambda expression with receiver definition
fun render(block: Canvas.() -> Unit): Canvas {
val canvas = Canvas()
// Use the lambda expression with receiver
canvas.block()
return canvas
}
fun main() {
render {
drawCircle()
// 🟠 Drawing a circle
drawSquare()
// 🟥 Drawing a square
}
}
В этом примере:
Класс
Canvasсодержит две функции, имитирующие рисование круга или квадрата.Функция
render()принимает параметрblockи возвращает экземпляр классаCanvas.Параметр
block— это лямбда-выражение с получателем, где получателем является классCanvas.Функция
render()создает экземпляр классаCanvasи вызывает лямбда-выражениеblock()для экземпляраcanvas, используя его в качестве получателя.Функция
main()вызывает функциюrender()с лямбда-выражением, которое передается параметруblock.-
Внутри лямбда-выражения, переданного функции
render(), программа вызывает функцииdrawCircle()иdrawSquare()для экземпляра классаCanvas.Поскольку функции
drawCircle()иdrawSquare()вызываются в лямбда-выражении с получателем, их можно вызывать напрямую, как если бы они находились внутри классаCanvas.
Лямбда-выражения с получателем полезны, когда нужно создать предметно-ориентированный язык (DSL). Поскольку вы можете обращаться к функциям-членам и свойствам получателя, не ссылаясь на него явно, код становится лаконичнее.
Рассмотрим пример настройки пунктов меню. Начнем с класса MenuItem и класса Menu, содержащего функцию добавления пунктов в меню под названием item(), а также список всех пунктов меню items:
class MenuItem(val name: String)
class Menu(val name: String) {
val items = mutableListOf<MenuItem>()
fun item(name: String) {
items.add(MenuItem(name))
}
}
Для начала передадим лямбда-выражение с получателем в качестве параметра функции (init) функции menu(), которая создает меню:
fun menu(name: String, init: Menu.() -> Unit): Menu {
// Creates an instance of the Menu class
val menu = Menu(name)
// Calls the lambda expression with receiver init() on the class instance
menu.init()
return menu
}
Теперь можно использовать DSL для настройки меню и создать функцию printMenu(), которая выводит структуру меню в консоль:
class MenuItem(val name: String)
class Menu(val name: String) {
val items = mutableListOf<MenuItem>()
fun item(name: String) {
items.add(MenuItem(name))
}
}
fun menu(name: String, init: Menu.() -> Unit): Menu {
val menu = Menu(name)
menu.init()
return menu
}
//sampleStart
fun printMenu(menu: Menu) {
println("Menu: ${menu.name}")
menu.items.forEach { println(" Item: ${it.name}") }
}
// Use the DSL
fun main() {
// Create the menu
val mainMenu = menu("Main Menu") {
// Add items to the menu
item("Home")
item("Settings")
item("Exit")
}
// Print the menu
printMenu(mainMenu)
// Menu: Main Menu
// Item: Home
// Item: Settings
// Item: Exit
}
//sampleEnd
Как видите, использование лямбда-выражения с получателем значительно упрощает код, необходимый для создания меню. Лямбда-выражения полезны не только для настройки и создания, но и для конфигурирования. Их часто используют при создании DSL для API, UI-фреймворков и конструкторов конфигурации, чтобы сделать код лаконичнее и упростить работу с его структурой и логикой.
В экосистеме Kotlin есть множество примеров этого шаблона проектирования, например функции buildList() и buildString() из стандартной библиотеки.
Практика
Упражнение 1
У вас есть функция fetchData(), принимающая лямбда-выражение с получателем. Измените лямбда-выражение, чтобы использовать функцию append() и получить следующий вывод: Data received - Processed.
fun fetchData(callback: StringBuilder.() -> Unit) {
val builder = StringBuilder("Data received")
builder.callback()
}
fun main() {
fetchData {
// Write your code here
// Data received - Processed
}
}
Упражнение 2
У вас есть класс Button и классы данных ButtonEvent и Position. Напишите код, который вызывает функцию-член onEvent() класса Button, чтобы инициировать событие двойного щелчка. Код должен вывести "Double click!".
class Button {
fun onEvent(action: ButtonEvent.() -> Unit) {
// Simulate a double-click event (not a right-click)
val event = ButtonEvent(isRightClick = false, amount = 2, position = Position(100, 200))
event.action() // Trigger the event callback
}
}
data class ButtonEvent(
val isRightClick: Boolean,
val amount: Int,
val position: Position
)
data class Position(
val x: Int,
val y: Int
)
fun main() {
val button = Button()
button.onEvent {
// Write your code here
// Double click!
}
}
Упражнение 3
Напишите функцию, создающую копию списка целых чисел, в которой каждый элемент увеличен на 1. Используйте предоставленный каркас функции, расширяющий List<Int> функцией incremented.
fun List<Int>.incremented(): List<Int> {
val originalList = this
return buildList {
// Write your code here
}
}
fun main() {
val originalList = listOf(1, 2, 3)
val newList = originalList.incremented()
println(newList)
// [2, 3, 4]
}
© 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-lambdas-receiver.html