第5章Lambda编程

第5章Lambda编程.png

5.1、Lambda表达式和成员引用

lambda表达式,简称lambda,本质上就是可以传递给其他函数的一小段代码,有了lambda,可以轻松的把通用的代码结构抽取成库函数,最常见的一种Lambda用途就是和集合一起工作。

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

  • 在代码中存储和传递一小段行为是常有的任务,例如:当一个事件发生的时候,运行这个事件处理器(比如点击事件),在老版本的Java中,可以用匿名内部类来实现,这种技巧可以工作但是语法太啰嗦了

  • 函数式编程提供了另外一种解决问题的方法:把函数当作值来对待。可以直接传递函数,而不需要先声明一个类再传递这个类的实例

  • 例子

    • 2fd7173258f8777f173f426c537746a70e4c2c01d2d2d82d7ada83e8dde53c04.png
    • b8a7273ea0b027ad5e0cc326112dc325cf4de2a0b5fb2d37cb10c5059bd9dcd4.png

2、Lambda和集合

  • 良好编程风格的主要原则之一是避免代码中的任何重复

  • 在没有lambda之前,对集合的操作,已经养成了什么东西都要自己实现的习惯,在Kotlin中这个习惯必须纠正

  • 例子

    • Person类,它包含了这个人的名字和年龄信息

    • 12daba720ece2b01a32e79f6adb182625ca8bb01069e23aff2a188f073109345.png

    • 假设现在有一个人的列表,需要找到列表中年龄最大的那个人

    • 如果完全不了解lambda,需要手动实现这个搜索功能,你可能需要先引入两个中间变量,一个用来保存最大的年龄,而另一个用来保存找到的此年龄的第一个人,然后迭代这个列表,不断更新这两个变量

    • 手动搜索实现

      • 29b10a53dc5c5ae1d5d700cf22a1274e198a4f603de0edca2b6ff644ddbbe88f.png

      • eb3a5153ded3cb84809f37f36b2cfd6e2e54b47d885275d16ed31172165580ca.png

      • 3d970538f7248e2ff43d7c2d2edcd12cef058a121b4e9798f5c336632bf42297.png

    • 使用库函数maxBy实现

      • a38ebd8df01f97d6fb1c1d141e24d3867341036c668e15bd8f8049260a20dc7d.png

      • 2fbaa907ed1039c9eede57b56a05c644776ed7deb1cb8fc0a6dc6332b6c8da9e.png

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

      • 这个例子中,集合元素是Person对象,用来比较的是存储在其age属性中的年龄

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

        • bdf74a6b1f05a10e5e35cf714d4f9d88eba0a33085561cc1d97dd45f65dcc337.png

3、Lambda表达式的语法

  • 一个lambda把一小段行为进行编码,你能把它当作值到处传递。它可以被独立地声明并存储到一个变量中,但是更常见的还是直接声明它并传递给函数。

  • Lambda表达式的语法

    • a49d2eb163971d2199269ce4ae649f28614be2aa7aa92c5e6684e5722ecde47c.png
    • Kotlin的lambda表达式始终用花括号包围;
      注意实参并没有用括号括起来箭头把实参列表和lambda的函数体隔开
  • 可以把lambda表达式存储在一个变量中,把这个变量当做普通函数对待(即通过相应的实参调用它)

    • 16111874b4088d857175e4d7e1154e08e734509f8199eec7c1ab34ec447a695f.png
    • 8a9bafbc554ae50a8eedda2afcb0f8119b324a576e6bde71393a0147430999ba.png
  • 还可以直接调用lambda表达式:

    • 065c6c5294dc0189ca1942bf3836169158b3f3ccda76f6c544228fa553844b38.png

    • 但是这样的语法毫无可读性,也没有什么意义(它等价于直接执行lambda函数体中的代码)

  • 如果确实需要把一小段代码封闭在一个代码块中,可以使用库函数run来执行它的lambda

    • 1e1c3878c360ebf31f4a131bbb58b29572818822f62cb9dee6ce8d91865bb414.png
    • 8b0221dab87a193437bff4acfde75770e10ba6d1e73032d89ff62402ecb36f99.png
  • 查找年龄最大的代码,如果不适用任何简明语法来重写这个例子,代码如下

    • 6313b18c4250effbf74684de511209d6e948ca0bd2b0c821f0ff3fac8f373f7e.png

    • 8040e0cbd78981f27d22b1a471f53df50a239b08f65b87efed6c6a82358e3eba.png

    • 这段代码一目了然:花括号的代码片段是lambda表达式,把它作为实参传给函数,这个lambda接收一个类型为Person的参数并返回它的年龄

    • 但是这段代码有点啰嗦。首先,过多的标点符号破坏了可读性。其次,类型可以从上下文推断出来并可以省略。最后,这种情况下不需要给lambda的参数分配一个名称

    • 让我们来改进这些地方,先拿花括号开刀

    • Kotlin有这样一种语法约定,如果lambda表达式是函数调用的【最后一个实参】,lambda可以放到括号的外边

    • 在这个例子中,lambda是【唯一的实参】,所以可以放到括号的后边

      • 6434bf48931c2d1ae912c8dad020be506bded90f1488ebfa32c367a5f3627bb5.png
    • 当lambda是函数唯一的实参时,可以去掉调用代码中的空括号对

      • 4eb638f65372ebe29680557407cfd59e007e63323f72fe11d3bcd08ef9eb5381.png
    • 三种语法形式含义都是一样的,但最后一种最易读。如果lambda是唯一的实参,可以省略掉括号,而当有多个实参时,既可以把lambda留在括号内来强调它是一个实参,也可以把它放在括号的外面,两种选择都是可行的

    • 如果想传递两个或更多的lambda,不能把超过一个的lambda放到外面,这时使用常规语法来传递它们通常是更好的选择

    • 省略lambda参数类型,和局部变量一样,如果lambda参数的类型可以被推导出来,就不需要显式地指定它

      • 90a7d0a022393252db2d8670174cdfcad4e2659a746c7e327de520f5dc3c181b.png
    • 这里maxBy函数为例,其参数类型始终和集合的元素类型相同,编译器知道你是对一个Person对象的集合调用maxBy函数,所以它能推断lambda参数也会是Person类型

    • 也存在编译器不能推断lambda参数类型的情况,另行讨论。可以遵循这样一条简单的规则:先不声明类型,等编译器报错后再指定他们

    • 可以指定部分实参的类型,而剩下的实参只用名称。如果编译器不能推导出其中一种类型,又或是显式的类型可以提升可读性,这种做法或许更方便

    • 最后简化,使用默认参数名称it代替命名参数。如果当前上下文期望的只有一个参数的lambda且这个参数的类型可以推断出来,就会生成这个名称

      • 9af705a862f3bfd64e7ec969389df0b3e8f864f9a03f521eb43e17a05e409e81.png
      • 代码简化过程
        b98b9615a124bf4215058a170c0becd0fd7ae8c89f236ded99e9407d6e06b475.png
    • 仅在实参名称没有显式地指定时这个默认的名称才会生成

    • 注意

      • it约定能大大缩短代码,但不应该滥用它。尤其是在嵌套lambda的情况下,最好显式地声明每个lambda的参数。否则很难搞清楚it引用的到底是哪个值。如果上下文中参数的类型或意义都不是很明朗,显式声明参数的方法也很有效
    • 如果用变量存储lambda,那么就没有可以推断出参数类型的上下文,所以必须显示地指定参数类型

      • 781f6aebb4623cb1a98eac3f5c0cde29b0579f5ddf558adc819292a29d00906e.png
    • 迄今为止,例子都是由单个表达式或语句构成的lambda,其实它可以包含更多的语句

      • d1816a2c5d52a43e2c28df3a33a454aa57150fe10984ac66fc29bdba62778730.png
      • 3160a626d97f687a373d538c636aa13eb213f30576f86fb8be7a0f5ccbfcc71a.png
  • 复杂调用,回顾joinToString函数,Kotlin标准库中也有定义,不过,它可以接收一个附加的函数参数,这个函数可以用toString函数以外的方法来把一个元素转换成字符串

    • 例子

      • 只打印人的名字
      • 6e9e117f4b097040608d6bc7f8b9c1697652bbcdebd88f6c63b1580db8dc69f6.png
      • 09b9001be62221e8025ca8ba428c2ee972d6676e579ad38f5726096772e0433b.png
      • 重写这个调用,把lambda放在括号外
      • 6c90b1767bf8f61ed24b2e4b05daa53f6e9bd11b708c4f73114482b2e8d353d0.png
      • 5fbb316548a1e75b111bcbe6bccd1b88ba83f077a80664482745987af429ab26.png

4、在作用域中访问变量

  • 和lambda表达式形影不离的概念:从上下文中捕捉变量

  • 当在函数内声明一个匿名内部类的时候,能够在这个匿名类内部引用这个函数的参数和局部变量

  • lambda可以做同样的事情,如果在函数内部使用lambda,也可以访问这个函数的参数

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值