《Kotlin 实战》- 5 Lambda 编程

  • lambda 本质是可以传递给其他函数的一小段代码。

5.1 Lambda 表达式和成员引用

  • 函数式编程与 lambda 表达式:函数式编程是把函数当做值来对待可以直接传递函数;lambda 表达式使得代码更简洁,不需要声明函数,可以高效地直接传递代码块作为函数参数。
button.setOnClickListener {/* 点击后的执行动作 */} // 注意是大括号
  • Lambda 语法:

    { x: Int, y: Int -> x + y}
    
    • 其中 -> 前的部分为参数,后面部分为函数体。注意到实参并没有用括号括起来,实参和函数体使用了 -> 符号隔开。
    • 可以把 lambda 表达式存储在一个变量中,这个变量当做普通函数对待
  • 看一下 lambda 表达式简写的演变过程:

// 前期准备
 val people = listOf(Person("Alice", 29), Person("Bob", 31))
 // maxBy 方法的系统声明
 fun <T, R : Comparable<R>> Iterable<T>.maxBy(selector: (T) -> R): T? 
// 原始方式
people.maxBy({ p: Person -> p.age })
// lambda 表达式是函数调用的最后一个实参,可放到括号外面
people.maxBy(){ p: Person -> p.age }
// lambda 是函数唯一实参,可以去掉空括号对
people.maxBy { p: Person -> p.age }
// 与局部变量一样,如果 lambda 参数类型可以被推到,就可以省略类型
// 也存在不能被推到的情况,可以遵循这样的规则:先不声明类型,编译器报错后再声明
people.maxBy {p -> p.age}
// 当只有一个参数并且参数类型可以推导,就可以使用默认参数名称 it 代替命名参数
people.maxBy { it.age }
  • 当实参有多个 lambda 时,不能把超过一个 lambda 放到外面,所以都放在 () 倒是更好的选择

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

    • 与 Java 不同的是,Kotlin 中不会仅限于访问 final 变量,在 lambda 内部也可以修改这些变量。
    • 从 lambda 内访问外部变量,称这些变量被 lambda 捕捉
  • 成员引用:Person:: age,类后(比如此处的 age)可以是函数也可以是属性,并且都无需加 ()。这样对于已经定义好的函数,也可以方便的作为参数传递。

5.2 集合的函数式API

  • 函数式编程风格在操作集合时提供了很多优势,kotlin 也加入了不少库函数来帮助解决集合问题。
  • filter :遍历集合并选出应用给定 lambda 后返回 true 的那些元素(即选出符合条件(lambda 函数)的集合元素,组成新集合
  • map:对集合中每个元素应用给定的 lambda 并把结果收集到一个新集合;
  • all:集合中是否所有元素都符合某条件(lambda),返回值是 boolean 类型;
  • any:集合中是否存在元素符合某条件,同样返回 Boolean 类型;
  • find:返回第一个符合条件的元素;
  • count:返回符合条件的元素总个数;
  • groupBy:把集合元素按照某特征(lambda)划分成不同的分组,返回是一个 map,key 为 lambda 中的条件,value 是列表集合中的元素;
  • flatMap:把结合中所有元素按照 lambda 做变化,然后想得到的结果“平铺”,返回平铺后的集合;
  • 注意:使用 lambda 表达式的代码看起来简单,有时候却掩盖了底层操作的复杂性,很容易写出不必要的重复计算的逻辑,尤其是对于集合的操作,产生不必要的循环或重复。所以始终要牢记你写的代码在干什么!

5.3 惰性集合操作:序列

  • 上面那些处理链表的函数,在链式调用时往往每一步都会创建新的链表,当处理大数据量时,效率较低。使用序列可以避免创建这些临时中间对象。

  • Kotlin 惰性集合操作的入口就是 Sequence 接口 。这个接口表示的就是一个可以逐个列举元素的元素序列。 Sequence 只提供了一 个方法 iterator,用来从 序列中获取值。Sequence 接口的强大之处在于其操作的实现方式 。序列中的元素求值是惰性的。因此,可以使用序列更高效地对集合元素执行链式操作,而不需要创建额外的集合来保存过程中产生的中间结果。可以调用扩展函数 asSequence 把任意集合转换成序列,调用 toList 来做反向转换。

  • listOf(1,2,3,4).asSequence().map{ it*it }.filter{ it>3 }.toList()
    
  • 这种操作其实主要分两步:中间操作和末端操作。中间操作始终都是惰性的,末端操作(toList() )触发执行了所有的延期计算。

  • 原理是“及早求值”,也就是会把序列中的元素,依次处理所有过程,这样有可能省去部分处理过程。比如我们把上例最后的 filter 变为 find,那么如果是对集合处理,会先所有元素求平方,再找第一个大于 3 的元素,而对于序列处理的话,会从第一个元素开始,先求平方,再看结果是否大于 3,如此找到第一个大于 3 的值就宣告结束了。

5.4 使用 Java 函数式接口

  • Kotlin 的 lambda 可以无缝地和 Java Api 互操作。

  • 把只含有一个方法的接口成为函数式接口,Android 中 OnClickListener,java 中比如 Runnable Callable 等都是函数式接口,Kotlin 允许你在调用接收函数式接口作为参数的方法时使用 lambda。

  • // java 中的声明
    public class View{
      public void setOnClickListener(OnClickListener l){...}
    }
    // Kotlin 调用
    button.setOnClickListener{...}
    
  • 参数可以有多个,只要含有函数式接口类型的参数,就可以使用 lambda

  • 实现原理:在 kotlin 中,每个 lambda 表达式都会被编译成一个匿名类,如果 lambda 捕捉了变量,每个被捕捉的变量会在匿名类中有对应的字段。

5.5 带接收者的 lambda:with 与 apply

  • With:可以用它对同一个对象执行多次操作,而无需反复把对象的名称写出来。

  • fun alphabet()= with(StringBuilder()){
      for (letter in 'A'..'Z'){
          append(letter)
      }
      toString()
    }
    
    • with 实际上是一个接收两个参数的函数,这个例子中两个参数是 StringBuilder 对象和一个 lambda,这里利用了把最后的 lambda 放到括号外的约定,这样看起来更像是内建的语言功能。并且示例代码中 lambda 内部省略了 this 引用。
  • apply:和 with 很像,但 apply 始终返回作为实参传递给它的对象。

  • fun alphabet()= StringBuilder().apply {
      for (letter in 'A'..'Z'){
          append(letter)
      }
    }.toString()
    
  • 二者区别:

    • 显然,with 是库函数,apply 是扩展函数
    • with 返回的是最后一行表达式(的值),apply 返回的是apply 其实是实参传进来的对象(接收者对象)。

5.6 小结

  • Lambda 允许你把代码块当作参数传递给函数。
  • Kotlin 可以把 lambda 放在括号外传递给函数,而且可以用 it 引用单个的 lambda 参数。
  • lambda 中的代码可以访问和修改包含这个 lambda 调用的函数中的变量。
  • 通过在函数名称前加上前缀 ::,可以创建方法、构造方法及属性的引用,并用这些引用代替 lambda传递给函数。
  • 使用像 filter、map、all、any 等函数,大多数公共的集合操作不需要手动迭代元素就可以完成。
  • 序列允许你合并一个集合上的多次操作 ,而不需要创建新的集合来保存中间结果。
  • 可以把 lambda 作为实参传给接收 Java 函数式接口(带单抽象方法的接口,也叫作 SAM 接口)作为形参的方法。
  • 带接收者的 lambda 是一种特殊的 lambda,可以在这种 lambda 中直接访问一个特殊接收者对象的方法。
  • with 标准库函数允许你调用同一个对象的多个方法,而不需要反复写出这个对象的引用 。apply 函数让你使用构建者风格的 API 创建和初始化任何对象。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,826评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,968评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,234评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,562评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,611评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,482评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,271评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,166评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,608评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,814评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,926评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,644评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,249评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,866评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,991评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,063评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,871评论 2 354