Kotlin 知识梳理(5) - lambda 表达式和成员引用

Kotlin 知识梳理系列文章

Kotlin 知识梳理(1) - Kotlin 基础
Kotlin 知识梳理(2) - 函数的定义与调用
Kotlin 知识梳理(3) - 类、对象和接口
Kotlin 知识梳理(4) - 数据类、类委托 及 object 关键字
Kotlin 知识梳理(5) - lambda 表达式和成员引用
Kotlin 知识梳理(6) - Kotlin 的可空性
Kotlin 知识梳理(7) - Kotlin 的类型系统
Kotlin 知识梳理(8) - 运算符重载及其他约定
Kotlin 知识梳理(9) - 委托属性
Kotlin 知识梳理(10) - 高阶函数:Lambda 作为形参或返回值
Kotlin 知识梳理(11) - 内联函数
Kotlin 知识梳理(12) - 泛型类型参数


一、本文概要

本文是对<<Kotlin in Action>>的学习笔记,如果需要运行相应的代码可以访问在线环境 try.kotlinlang.org,这部分的思维导图为:

二、Lambda 表达式和成员引用

Lambda表达式,本质上是可以 传递给函数的一小段代码,可以轻松地把通用的代码结构抽取成库函数,Kotlin标准库就大量地使用了它们。

2.1 Lambda 简介:作为函数参数的代码块

Lambda的应用场景有:

  • 当一个事件发生的时候运行这个事件处理器
  • 把这个操作应用到这个数据结构中所有的元素上

Java中,可以用匿名内部类来实现,但是它的语法很啰嗦,下面我们演示用Lambda来实现点击监听:

button.setOnClickListener { /* 点击后执行的动作 */}

2.2 Lambda 和集合

我们对集合执行的大部分任务都遵循几个通用的模式,所以实现这几个模式的代码应该放在一个库里,下面我们演示一个例子:将Person数据类放到一个集合当中,并从中选出年龄最大的一个人。


运行结果为:

这上面的例子用到了集合上的maxBy函数,它只需要一个实参:一个函数,指定比较哪个值来找到最大的元素,花括号中的代码{ it.age }就是实现了这个逻辑的lambda,它接收一个集合中的元素作为实参(使用it引用它)并且返回用来比较的值,在上面的例子中:

  • 集合元素是Person对象
  • 用来比较的值是存储在其age属性中的值

如果lambda刚好是 函数或者属性的委托,可以用 成员引用 替换。

people.maxBy(Person :: age)

2.3 Lambda 表达式语法

一个Lambda表达式把一小段行为进行编码,你能把它 当做值到处传递,它可以被 独立地声明并存储到一个变量中,但是最常见的还是直接声明它并传递给函数,下面是一个Lambda表达式的语法,->前为 参数,后为 函数体,始终用 花括号包围

{x : Int, y : Int -> x + y}

2.3.1 将 Lambda 表达式存储在变量中

可以将Lambda表达式存储在一个变量中,把这个变量当做普通函数对待(即通过相应的实参调用它):

2.3.2 直接调用 Lambda 表达式

如果需要把一小段代码封闭在一个代码块中,可以使用库函数run,这种调用和内建语言结构一样高效且不会带来额外运行时开销:


运行结果为:

2.3.3 Lambda 表达式的简化过程

现在,让我们回到最开始寻找集合中年龄最大的人的例子,它原本的调用方法如下,maxBy函数接收一个lambda表达式{ p : Person -> p.age }作为参数:

people.maxBy({ p : Person -> p.age })

上面这段代码的解释为:花括号中的代码片段是lambda表达式,把它作为实参传给函数,这个lambda接收一个类型为Person的参数并返回它的年龄。下面,我们一起来看一下如何简化这个表达式:

  • 第一步:Kotlin有一个语法规定,如果lambda表达式是函数调用的 最后一个实参,它可以 放到括号的外边,因此上面的例子简化为:
//第一步:将 lambda 表达式放到括号的外边。
people.maxBy() { p : Person -> p.age }
  • 第二步:当lambda是函数 唯一的实参,还可以 去掉调用代码中的空括号对
//第二步:去掉空括号对。
people.maxBy { p : Person -> p.age }
  • 第三步:和局部变量一样,如果lambda参数的类型可以被推倒出来,你就不需要显示地指定它,以maxBy函数为例,其 参数类型始终和集合的元素类型相同,因此编译器知道你是对Person对象的集合调用maxBy函数,可以简化为:
//第三步:省略 lambda 参数类型。
people.maxBy { p -> p.age }

但是如果我们用变量存储lambda,那么就没有可以推断出参数类型的上下文,所以你必须显示地指定参数类型:

//无法推断出参数的类型,必须显示地指定参数的类型。
val getAge = { p : Person -> p.age }
people.maxBy (getAge)
  • 第四步:如果当前上下文期望的是 只有一个参数的 lambda ,并且这个参数的类型可以推断出来,那么可以使用默认参数名称it代替命名参数:
//第四步:使用默认参数名称。
people.maxBy { it.age }

2.3.4 又见 joinToString 函数

Kotlin 知识梳理(2) - 函数的定义与调用 中,我们通过joinToString介绍了命名参数和默认参数值的用法,实际上在标准库中也有定义这个函数,不同之处在于它可以接收一个附加的函数参数,这个函数可以用toString函数以外的方法来把一个 元素转换成字符串,下面显示如何 只打印出人的名字


运行结果为:

2.3.5 lambda 表达式包含更多语句

lambda表达式可以包含更多的语句,最后一个表达式就是lambda的结果:


运行结果为:

2.4 在作用域中访问变量

当在函数内声明一个匿名内部类时,能够在这个匿名内部类引用这个函数的参数和局部变量,也可以用lambda做同样的事情,如果在函数内部使用lambda,也可以访问这个函数的参数,还有在lambda之前定义的局部变量。

下面我们用标准库函数forEach来展示这种行为,它是最基本的集合操作函数之一:它所做的全部事情就是在集合中的每一个元素之上都调用给定的lambda


运行结果为:

2.4.1 在 lambda 中改变局部变量

Kotlin中不会仅限于访问final变量,在lambda内部也可以修改这些变量,下面的代码中对给定的相应状态码set分别进行计数。


Kotlin中,它允许在lambda内部访问非final变量甚至修改它们。从lambda内访问外部变量,我们称这些 变量被 lambda 捕捉,就像上面例子中的clientErrorsserverErrors

默认情况下,局部变量的生命周期被限制在声明这个局部变量的函数当中,但是如果它被lambda捕捉了,使用这个变量的代码可以被存储并稍后执行,原理为:

  • 当捕捉final变量时,它的值和使用这个值的lambda代码一起存储。
  • 对非final变量,它的值被封装在一个包装器中,这样你就可以改变这个值,而对这个包装器的引用会和lambda代码一起存储。

2.4.2 捕捉可变变量

Java只允许捕捉final变量,而当你想捕捉可变变量的时候,可以使用两种技巧:

  • 声明一个单元素的数组,其中存储可变值
  • 创建一个包装类的实例,其中存储要改变的值的引用

这样,当捕捉了一个可变变量var的时候,它的值被作为Ref类的一个实例被存储下来,Ref变量是final的能轻易被捕捉,然后实际值存储在其字段中,并且可以在lambda内被修改。

2.5 成员引用

2.5.1 基本概念

在上面的例子中,我们演示了 如何让你把代码块作为参数传递给函数,但是如果要当做参数传递的代码已经被定义成了函数,这时候就需要 把函数转换成一个值,这种方式称为 成员引用

val getAge = Person :: age

它提供了简明的语法,来创建一个 调用单个方法或者访问单个属性的函数值,双冒号把 类名称你要引用的成员(一个方法或者属性)名称 隔开。

成员引用和调用该函数的lambda具有一样的类型,所以可以互换使用:

people.maxBy(Person :: age)

2.5.2 引用顶层函数

除此之外,还可以引用顶层函数,这里我们省略了类名称,直接以:开头,成员引用::salute被当作实参传递给库函数run,它会调用相应的函数:


运行结果为:

2.5.3 存储或者延期执行创建类实例的动作

我们还可以使用 构造方法引用 存储或者延期执行创建类实例的动作,构造方法引用的形式是 在双冒号后指定类的名称

2.5.4 引用扩展函数

我们还可以以同样的方式引用扩展函数,这里我们定义一个扩展函数isAdult方法,选出年龄大于21的人。


运行结果为:

参考文章

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容