Kotlin中的Lambda编程

1.集合的创建与遍历

传统意义上的集合主要是List和Set,再广泛一点的话,像Map这样的键值对数据结构也可以包含进来。List,Set和Map再Java中都是接口,List主要的实现类是ArrayList和LinkedList,Set的主要实现类是HashSet,Map的主要实现类的HashMap。
比如现在创建一个包含许多水果名称的集合,在java中我们会首先创建一个ArrayList的实例,然后将水果的名称一个个添加到集合中。在kotlin也可以这么做。

fun main(){
    val list = ArrayList<String>()
    list.add("Apple")
    list.add("Banana")
    list.add("Orange")
    list.add("Pear")
    list.add("Grape")
}

但是这种初始化集合的方式比较繁琐,为此Kotlin专门提供了一个内置的listOf()函数来简化初始化集合的写法,如下所示

  val list = listOf("Apple", "Banana", "Pear", "Grape")

在循环语句中,for-in循环不仅可以用来遍历区间,还可以用来遍历集合。现在我们就尝试一下使用for-in循环来遍历这个水果集合。

fun main(){
    val list = listOf("Apple", "Banana", "Pear", "Grape")
    for(fruit in list){
        println(fruit)
    }
}

在这里插入图片描述
不过需要注意的是,listOf()函数创建的是一个不可变的集合,不可变集合指的就是该集合只能用于读取,我们无法对集合进行添加、修改或删除操作。
当我们需要一个可变的集合,就可以使用mutableListOf()函数,示例如下:

fun main(){
    val list = mutableListOf("Apple", "Banana", "Pear", "Grape")
    list.add("orange")
    for(fruit in list){
        println(fruit)
    }
}

在这里插入图片描述
Set集合和List集合的用法几乎是一模一样,只是将创建集合的方式换成了setOf()和mutableSetOf()函数而已。

    val set = setOf("Apple", "Banana", "Pear", "Grape","orange")
    for(fruit in set){
        println(fruit)
    }

需要注意的是,Set集合底层是使用hash映射机制存放数据的,因此集合中的元素无法保证有序,这是和List集合最大的不同之处。
最后再来看一下Map集合。Map是一种键值对形式的数据结构,Map用法是先创建一个HashMap的实例,然后将一个个键值对数据添加到Map中。比如这里我们给每种水果设置一个对应的编号,可以这样写

    val map = HashMap<String, Int>()
    map.put("Apple",1)
    map.put("Banana",2)
    map.put("Orange",3)
    map.put("Pear",4)
    for(fruit in map){
        println(fruit)
    }

因为这种写法和Java语法是比较相似的,因此可能比较好理解,但是其实在Kotlin中并不建议使用put()和get()方法来对Map进行添加和读取数据操作,而是推荐使用一种类似于数组下标的语法结构,比如Map中添加一条数据就可以这么写:
map[“Apple”] = 1
而从Map中读取一条数据就可以这么写
val number = map[“Apple”]
经过上面的代码优化后可以这么写

    val map = HashMap<String, Int>()
    map["Apple"] = 1
    map["Banana"] = 2
    map["Orange"] = 3
    map["Pear"]=4

这还不是最简便的写法,因为Kotlin给我们提供了一对mapOf()和mutableMapOf()函数来继续简化Map的用法。在mapOf()函数中,我们可以直接传入初始化的键值对组合来完成对Map集合的创建:

    var map= mapOf("Apple" to 1,"Banana" to 2,"orange" to 3,"Pear" to 4)

这个to不是关键,涉及到了infix函数。

    var map= mapOf("Apple" to 1,"Banana" to 2,"orange" to 3,"Pear" to 4)
    for((fruit,number) in map ){
        println("fruit is $fruit"+" ,number is $number")
    }

在for in循环中可以将Map键值对变量一起声明到了一对括号里面,这样当进行循环遍历时,每次遍历的结果就会赋值给这两个键值对变量,最后将他们的值打印出来。
在这里插入图片描述

2.集合的函数式API

在水果集合里面找到单词最长的那个水果,如果不用Lambda表达式,你可能会这么写

    val list = listOf("Apple", "Banana", "Pear", "Grape", "orange")
    var maxLengthFruit=""
    for (fruit in list){
        if(fruit.length>maxLengthFruit.length){
            maxLengthFruit=fruit
        }
    }
    println("max length Fruit is $maxLengthFruit")

如果我们使用集合的函数式API

    val maxLengthFruit = list.maxBy { it.length }
    println("max length Fruit is $maxLengthFruit")

这里是借助了Lambda表达式
Lambda表达式的语法结构为:{参数名1:参数类型,参数名2:参数类型->函数体}
首先最外层是一对大括号,如果有参数传入到Lambda表达式的话,我们还需要声明参数列表,参数列表的结尾使用一个->符号,表示参数列表的结束以及函数体的开始,函数体中可以编写任意行代码,并且最后一行代码会自动作为Lambda表达式的返回值
maxBy函数就是一个普通函数,只不过它接收的是一个Lambda类型的参数,并且会在遍历集合时将每次遍历的值作为参数传递给Lambda表达式。maxBy函数的工作原理时将根据我们传入的条件来遍历集合,从而找到该条件下的最大值,比如说想要找到单词最长的水果,那传入的条件就是单词的长度了。
知道了maxBy函数的工作原理后,我们就可以套用刚才的Lambda表达式的语法结构,并将它传入到maxBy函数中

    val lambda={fruit:String ->fruit.length}
    val maxLengthFruit=list.maxBy(lambda)//按照lambda表达式的条件进行查找最大长度
    println(maxLengthFruit)

可以指导maxBy函数实质上就是接收了一个lambda参数而已,并且这个Lambda参数是完全按照语法结构:参数名:参数类型->函数体来定义的。
这种写法还是可以再进行简化的,可以直接将Lambda表达式传入maxBy函数中

    val maxLengthFruit=list.maxBy({fruit:String->fruit.length})

Kotlin规定,当Lambda参数是函数最后一个参数时,可以将Lambda表达式移到函数括号的外面

   val maxLengthFruit=list.maxBy(){fruit:String->fruit.length}

接下来,如果Lambda参数是函数的唯一一个参数的话,还可以将函数的括号省略

   val maxLengthFruit=list.maxBy{fruit:String->fruit.length}

接下来,还可以继续进行简化,由于Kotlin拥有出色的类型推导机制,Lambda表达式中的参数列表其实在大多数情况下不必要声明参数类型,因此代码进一步简化为

    val maxLengthFruit=list.maxBy{fruit->fruit.length}

最后当Lambda表达式的参数列表中只有一个参数时,也不必要声明参数名,而是可以使用it关键字来代替

    val maxLengthFruit=list.maxBy{it.length}

通过推导过程,得到了和开始的那一段函数API一模一样的写法。
集合中的map函数也是一种最常用的一种函数API,它用于将集合中的每个元素都映射成一个另外的值,映射规则在Lambda表达式中指定,最终生成一个新的集合。比如这里将所有水果名都变成大写模式,可以这样写

fun main(){
    val list = listOf("Apple", "Banana", "Pear", "Grape", "orange")
    val newList = list.map { it.toUpperCase() }
    for (fruit in newList){
        println(fruit)
    }
}

可以看到,我们在map函数的Lambda表达式中指定将单词转换成了大写模式,然后遍历这个新的集合。
在这里插入图片描述
map的功能很强大,可以根据我们的需求对集合中的元素进行任意的映射转换,除此之外,还可以将水果名全部转换为小写,或者只取单词的首字母,甚至是转换成单词长度这样的数字集合,只需在Lambda表达式中编写需要的逻辑即可
例如:只取单词首字母

    val newList = list.map { it[0]}
    for (fruit in newList){
        println(fruit)
    }

在这里插入图片描述
接下来是另外一个比较常用的函数API–filter函数。顾名思义,filter函数是用来过滤集合中的数据的,它可以单独使用,也可以配合刚才的map函数一起使用。
比如我们只想保留5个字母以内的水果,就可以借助filter函数来实现

fun main(){
   val list = listOf("Apple", "Banana", "Pear", "Grape", "orange")
    val newList = list.filter { it.length<=5 }.map { it.toUpperCase() }
    for (fruit in newList){
        println(fruit)
    }
}

可以看到同时使用了filter和map函数,并通过Lambda表达式将水果单词长度限制在5个单词以内并将水果名映射为大写字母
在这里插入图片描述
上述代码中,我们是先调用filter函数再调用map函数,如果改成先调用map函数再调用filter函数,效率会差很多,因为你这样相当于先将所有元素映射转换后再进行过滤,这是完全不必要的。先将所有元素过滤后再进行映射转换,这样明显高效许多。

接下来还有两个比较常用的函数式API–any和all函数。其中any函数用于判断集合中是否至少存在一个元素满足指定条件,all函数用于判断集合中是否所有元素满足指定条件

fun main(){
    val list = listOf("Apple", "Banana", "Pear", "Grape", "orange")
    val anyResult = list.any { it.length <= 5 }
    val allResult = list.all { it.length <= 5 }
    println("anyResult is "+anyResult+", allResult is "+allResult)
}

此时any函数表示集合中是否存在5个字母以内的单词,而all函数表示集合中是否所有单词都在5个字母以内。
在这里插入图片描述

3.Java函数式API的使用

Kotlin中调用Java方法时也可以使用函数式API,只不过这是有一定条件限制的。具体来讲,如果我们在Kotlin代码中调用了一个Java方法,并且该方法接收一个Java单抽象方法接口参数,就可以使用函数式APIJava单抽象方法接口指的是接口中只有一个待实现方法,如果接口中有多个待实现方法,则无法使用函数式API
例如:Java原生API中有一个最为常见的抽象方法接口–Runnable接口。这个接口中只有一个待实现的run()方法,定义如下:

public interface Runnable{
void run();
}

根据前面的定义,对应任何一个Java方法,只要它接收Runnable参数,就可以使用函数式API。那么什么Java方法接收了Runnable参数呢?这就很多了,不过Runnable接口主要还是结合线程一起使用的。
Thread类的构造方法中接收一个Runnable参数,我们可以使用Java代码创建并执行一个子线程:

new Thread(new Runnable(){
@Override
public void run(){
System.out.println("Thread is running");
}
}).start();

这里使用匿名内部类的写法,我们创建了一个Runnable接口的匿名类实例,并将它给了Thread类的构造方法,最后调用Thread类的start()方法执行这个线程。
如果将这断代码翻译为Kotlin版本,写法如下:

    Thread(object:Runnable{
        override fun run() {
            println("Thread is running")
        }
    }).start()

Kotlin中匿名内部类的写法和Java有一点区别,由于Kotlin完全舍弃了new关键字,因此创建匿名内部类实例的时候就不能再使用new了,而是改用了object关键字。
还有目前Thread类的构造方法时符合Java函数式API的使用条件的,下面我们就看看如何对代码进行精简

    Thread(Runnable { println("Thread is running") }).start()

因为Runnable类中只有一个待实现的方法,即使这里没有显式地重写run()方法,Kotlin也能自动明白Runnable后面的Lambda表达式就是run()方法中要实现的内容。
另外,如果一个Java方法的参数列表中不存在一个以上Java单抽象方法接口参数,我们还可以将接口名进行省略。(也就是说Thread方法只需要Runnable一个接口)

    Thread( { println("Thread is running") }).start()

当Lambda表达式还是方法的最后一个参数时,可以将Lambda表达式移到方法括号的外面。同时,如果Lambda表达式还是方法的唯一一个参数,还可以将方法的括号省略。

    Thread{ println("Thread is running") }.start()

在这里插入图片描述
当我们在Kotlin中调用Android SDK接口时,就很有可能会用到这种Java函数式API写法
举个例子:Android中有一个经常用到的点击事件接口OnClickListener,其定义如下:

public interface OnClickListener{
void onClick(View v);
} 

可以看到这又是一个单抽象方法接口。假设我们拥有一个按钮button实例,然后使用java代码去注册这个按钮的点击事件

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

而用Kotlin代码实现同样的功能,就可以使用函数式API的写法来对代码进行简化,结果如下:

button.setOnClickListener{

}

Java函数式API的使用都限定于从Kotlin中调用Java方法,并且抽象方法接口也必须是用Java语言定义的。
例如:ListView中的点击事件方法setOnItemClickListener接收一个OnItemClickListener接口对象

    /**
     * Register a callback to be invoked when an item in this AdapterView has
     * been clicked.
     *
     * @param listener The callback that will be invoked.
     */
    public void setOnItemClickListener(@Nullable OnItemClickListener listener) {
        mOnItemClickListener = listener;
    }

OnItemClickListener 接口

  public interface OnItemClickListener {

        /**
         * Callback method to be invoked when an item in this AdapterView has
         * been clicked.
         * <p>
         * Implementers can call getItemAtPosition(position) if they need
         * to access the data associated with the selected item.
         *
         * @param parent The AdapterView where the click happened.
         * @param view The view within the AdapterView that was clicked (this
         *            will be a view provided by the adapter)
         * @param position The position of the view in the adapter.
         * @param id The row id of the item that was clicked.
         */
        void onItemClick(AdapterView<?> parent, View view, int position, long id);
    }

因为setOnItemClickListener方法接收一个Java单抽象方法接口参数,就可以使用函数式API,由于onItemClick接口中含有四个参数(一个以上),使用时简化为

        list_view.setOnItemClickListener { parent, view, position, id ->

        }

当如果只使用到position这个参数的时候,kotlin允许我们将没有用到的参数使用下划线来替代,因此可以写成下面这种写法

        list_view.setOnItemClickListener { _, _, position, _ ->

        }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值