中级教程: 带接受者的 Lambda 表达式
在这一章中, 你将学习在另一种函数类型 Lambda 表达式中如何使用接受者对象, 以及它们如何帮助你创建一个特定领域专用语言(Domain-Specific Language, DSL).
带接受者的 Lambda 表达式
在初学者教程中, 你已经学习了如何使用 Lambda 表达式. Lambda 表达式也可以带有接受者. 这种情况下, Lambda 表达式能够访问接受者对象的任何成员函数或属性, 而不必每次都明确的指明接受者对象. 没有了这些额外的引用, 你的代码会变得更加易于阅读和维护.
带接受者的 Lambda 表达式的语法与定义函数类型时不同. 首先, 请写下你想要扩展的接受者类型. 之后, 是一个 . 号, 之后写下你的函数类型定义的其它部分. 例如:
这个函数类型:
接受者类型是
MutableList<Int>.括号
()之内没有函数参数.没有返回值:
Unit.
我们来看看下面的实例, 它扩展 StringBuilder 类:
在这个示例中:
接受者类型是
StringBuilder类.Lambda 表达式的函数类型没有函数参数
(), 也没有返回值Unit.Lambda 表达式调用
StringBuilder类的append()成员函数, 使用字符串"Hello!"作为函数参数.创建了
StringBuilder类的一个实例.Lambda 表达式赋值给变量
appendText, 并在stringBuilder实例上调用.使用
toString()函数,stringBuilder实例被转换为字符串, 并通过println()函数打印输出.
如果你想要创建一个特定领域专用语言(Domain-Specific Language, DSL), 带接受者的 Lambda 表达式会非常有用. 因为你可以访问接受者对象的成员函数和属性, 而不必明确引用接受者, 你的代码会变得更加精简.
为了演示这一点, 我们来考虑一个配置菜单中项目的示例. 我们从一个 MenuItem 类和一个 Menu 类开始, Menu 类包含一个向菜单中添加项目的函数, 名为 item(), 以及一个包含所有项目的列表, 名为 items:
我们使用一个带接受者的 Lambda 表达式, 将它作为函数参数 (init) 传递给 menu() 函数, 这个函数构建一个菜单, 作为开始点. 你会注意到, 这段代码使用了与前面的 StringBuilder 类的示例类似的方案:
现在你可以使用这个 DSL 来配置一个菜单, 并创建一个 printMenu() 函数, 将菜单结构打印输出到控制台:
如你所见, 使用带接受者的 Lambda 表达式 大大的简化了创建你的菜单所需要的代码. Lambda 表达式不仅仅可以用于设置和创建, 也可以用于配置. 它们普遍用于构建 API, UI 框架, 以及配置构建器的 DSL, 以生成精简的代码, 让你能够更容易的专注于底层代码结构和逻辑.
Kotlin 的生态环境中存在这种设计模式的很多例子, 例如标准库中的 buildList() 和 buildString() 函数.
实际练习
习题 1
你有一个 fetchData() 函数, 参数是一个带接受者的 Lambda 表达式. 请更新 Lambda 表达式, 使用 append() 函数, 让你的代码的输出成为: Data received - Processed.
习题 2
你有一个 Button 类, 以及 ButtonEvent 和 Position 数据. 请编写代码, 触发 Button 类的 onEvent() 成员函数, 触发一个 double-click 事件. 你的代码应该打印输出 "Double click!".
习题 3
编写一个函数, 创建一个整数 List 的副本, 其中每个元素增大 1. 请使用已经提供的函数框架, 它使用一个 incremented 函数扩展了 List<Int>.