美文网首页
《Kotlin 实战》- 5 Lambda 编程

《Kotlin 实战》- 5 Lambda 编程

作者: Ronnie_火老师 | 来源:发表于2019-07-12 20:11 被阅读0次
    • 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 创建和初始化任何对象。

    相关文章

      网友评论

          本文标题:《Kotlin 实战》- 5 Lambda 编程

          本文链接:https://www.haomeiwen.com/subject/xutukctx.html