美文网首页
Kotlin之Lambda编程

Kotlin之Lambda编程

作者: code希必地 | 来源:发表于2020-09-20 22:32 被阅读0次

    1、Lambda的定义

    Lambda就是一小段可以作为参数传递的代码。正常情况下,我们向某个函数传参时只能传入变量,而借助Lambda却能传入一小段代码,那么多长的代码才能算一小段代码,Kotlin不建议很长的代码,可读性太差。
    接下来我们看下,Lambda表达式的语法结构:

    {参数名1:参数类型,参数名2:参数类型 -> 函数体}
    

    这是Lambda表达式最完整的语法结构。

    • 1、首先最外层一对大括号。
    • 2、如果有参数传入Lambda表达式,我们还需要声明参数列表。
    • 3、参数列表的结尾使用->符号,表示参数列表的结束以及函数体的开始,函数体中代码长度不限,并且最后一行代码表示Lambda表达式的返回值

    2、集合的函数式API

    2.1、maxBy

    集合的函数式API有很多个,这里并不打算讲解所有的函数式API的用法,而是重点学习函数式API的语法结构,也就是Lambda表达式的语法结构。
    先看一个需求:如何从List集合中找到单词最长的那个水果。

     val list = arrayListOf("Apple", "Banana", "Orange", "Pear")
            list.add("Watermelon")
            var maxLengthFruit = ""
            for (fruit in list) {
                if (fruit.length >= maxLengthFruit.length)
                    maxLengthFruit = fruit
            }
            println("单词最长的水果:"+maxLengthFruit)
    

    代码很简单,如果使用集合函数式API来实现的会更简单

     val list = arrayListOf("Apple", "Banana", "Orange", "Pear")
            list.add("Watermelon")
            var maxLengthFruit = list.maxBy { it.length }
            println("单词最长的水果:" + maxLengthFruit)
    

    list.maxBy { it.length }直接看这个还是很难理解的,maxBy就是一个普通的函数,只不过它接收的是一个Lambda类型的参数,并且会在遍历集合时将每次遍历的值作为参数传递给Lambda表达式。maxBy函数工作原理是根据我们传入的条件来遍历集合,从而找到该条件下的最大值,比如我们想找到单词最长的水果,那么条件自然是单词的长度。
    理解了maxBy的工作原理, 我们就先用标准的Lambda的语法结构作为参数,传递给maxBy函数。

     val list = listOf("Apple", "Banana", "Orange", "Pear", "Watermelon")
            val lambda = { fruit: String -> fruit.length }
            //注意这里是小括号,括号中接收的Lambda参数
            val maxLengthFruit = list.maxBy(lambda)
            println("单词最长的水果:" + maxLengthFruit)
    

    可以看到maxBy中接收的是一个Lambda参数而已,并且这个Lambda参数是完全按照Lambda的标准结构定义的,这段代码虽然可以正常工作,但是需要简化的点很多,下面我们一步步简化。

    • 1、首先我们不需要专门定义一个lambda变量,可以将lambda表达式传入maxBy函数中
    val maxLengthFruit = list.maxBy({ fruit: String -> fruit.length })
    
    • 2、Kotlin规定,当Lambda是函数的最后一个参数时,可以将Lambda表达式移到函数的括号外面
    val maxLengthFruit = list.maxBy(){ fruit: String -> fruit.length }
    
    • 3、如果Lambda是函数的唯一参数的话,可以省略函数的括号
    val maxLengthFruit = list.maxBy{ fruit: String -> fruit.length }
    
    • 4、由于Kotlin拥有出色的类型推导机制,所以Lambda的参数列表大多数情况下不需要声明参数类型
    val maxLengthFruit = list.maxBy{ fruit -> fruit.length }
    
    • 5、最后当Lambda表达式只有一个参数时,也没必要声明参数名称,可以用it代替
    val maxLengthFruit = list.maxBy{ it.length }
    

    经过一步步推导,终于简化完成了。接下来我们学习下几个集合中标常用的函数式API。

    2.2、集合中的map函数

    集合中的map函数的作用:是将集合中的每个元素映射成另外的值,映射的规则是在Lambda表达式中指定的,最终生成一个集合。比如这里我们想让水果名变成大写的

            val list = listOf("apple", "orange")
            val newList = list.map {  it.toUpperCase() }
            for (fruit in newList){
                println("fruit::"+fruit)
            }
    

    map函数的功能非常强大,它可以按照我们的要求对集合中的元素进行随意的映射转换,比如将水果名改成小写或者只取水果名的首字母,甚至是转换成单词长度的集合,只需在Lambda表达式中编写相应的逻辑即可。

    2.3、filter函数

    filter函数是用来过滤集合中的数据的,它可以单独使用也可以配合map函数来使用。比如我们只想保留5个字母以内的水果,就可以借助filter函数来完成,代码如下:

    val list = listOf("apple", "orange", "watermelon", "banana")
            val newList = list.filter { it.length <=5 }
                    .map { it.toUpperCase() }
            for (fruit in newList) {
                println(fruit)
            }
    

    另外值得一提的是,上述代码中我们先调用了filter函数再调用了map函数,如果改变调用的顺序,效果一样但是效率会低很多,其实很简单,因为这相当于对集合中所有元素都映射改变之后再过滤,肯定比只映射变换过滤后的效率低。

    2.4、all和any函数

    • 1、any:判断集合中至少一个元素满足指定条件
    • 2、all:判断集合中所有元素是否都满足指定条件
    val list = listOf("apple", "orange", "watermelon", "banana")
            val anyResult = list.any { it.length <= 5 } //返回true
            val allResult = list.all { it.length <= 5 } //返回false
            println("anyResult is "+anyResult+" allResult is "+allResult)
    

    3、Java函数式API的使用

    在Kotlin中调用Java方法时也可以使用函数式API,只不过这是有一定的条件限制的,如果我们在Kotlin中调用Java方法,那么这个方法接收一个Java单抽象方法接口参数时就可以使用函数式API。Java的单抽象方法接口指的是,接口中只有一个待实现的方法。比如我们常用的接口Runnable

    public interface Runnable {
        void run();
    }
    

    根据前面的讲解,对于任何一个Java方法只要它接收Runnable参数,就可以使用函数式API。比如我们在创建线程时就需要传入Runnable参数。

    new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println("子线程运行");
                }
            }).start();
    

    注意这里使用了匿名类的写法,我们创建了一个Runnable接口的匿名类然后作为参数传递给Thread的构造函数。那么用Kotlin如何实现呢?

     Thread(object :Runnable{
              override fun run() {
                  println("子线程运行")
              }
    
          }).start()
    

    Kotlin的匿名类的创建和Java有点区别,Kotlin中使用object创建匿名类实例。但是别忘了,Thread的构造方法是符合Java函数式API的使用条件的,下面看下如何精简。

    • 1、精简待实现方法
    Thread(Runnable {
                  println("子线程运行")
          }).start()
    

    这段代码明显精简很多,但也不会造成歧义,因为Runnable接口中只有一个待实现的方法,所以即使没有重写run()方法,Kotlin也能自动明白Runnable后面的Lambda表达式就是run()方法中实现的内容。
    - 2、精简接口
    如果Java方法的参数列表中只存在一个的Java单抽象方法接口参数,我们还可以对接口名进行省略。

    Thread({
                  println("子线程运行")
          }).start()
    
    • 3、精简括号
      和Kotlin函数式API一样,如果Lambda表达式是最后一个参数的话,可以将Lambda表达式一道小括号外面,并且如果只有这一个参数的话,可以省略小括号。
     Thread{
                  println("子线程运行")
          }.start()
    

    再举个我们在Android中常用的点击事件的接口View.OnclikListener,定义一个button的点击事件如下:

    button.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    
                }
            });
    

    下面我们根据我们上面的分析,再次简化一下:

    button.setOnClickListener(object: View.OnClickListener{
                override fun onClick(v: View?) {
                     println("button is clicked")
                }
            })
    //1、简化方法
    button.setOnClickListener(View.OnClickListener{
                println("button is clicked")
            })
    //2、简化接口
     button.setOnClickListener({
                println("button is clicked")
            })
    //3、简化括号
     button.setOnClickListener{
                println("button is clicked")
            }
    

    最后提醒一句:本小节学习的Java函数式API的调用只限定于从Kotlin中调用Java方法,并且单独抽象方法接口必须是Java语言定义的。这么定义的原因是因为:Kotlin中有专门的高阶函数来实现更加强大的自定义函数式API功能,从而不需要像Java这样借助单抽象方法接口来实现。

    相关文章

      网友评论

          本文标题:Kotlin之Lambda编程

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