集合的函数式API是用来入门Lambda编程的绝佳示例。所以下面我们先通过一个简单的列子来学一下kotlin中的集合。
需求:创建一个包含许多水果名称的集合。
传统写法:
val list = ArrayList<String>()
list.add("apple")
list.add("orange")
list.add("grape")
list.add("pear")
list.add("banana")
观察上述代码是不是觉得每次都要调list.add()有点重复啰嗦不够简洁呢,像这种明确知道在初始化阶段就要赋值的行为能不能简化呢,比如就像初始化数组一样,可以在创建的时候直接赋值,这个问题kotlin懂你,下面我们来看看kotlin简化版。
便捷写法:
val list = listOf("apple", "orange", "grape", "pear", "banana")
for (fruit in list) {
println(fruit)
}
( listOf()是Kotlin专门提供的一个内置函数,用来简化初始化集合的写法。for-in循环不仅可以用来遍历区间,还可以用来遍历集合 )
两种写法简洁性高下立判,不过需要注意的是:listOf()函数创建的是一个不可变集合,只能读取,不能进行增、删、改操作。那如果我们既要使用这种便捷的初始化操作,又要创建的集合可变怎么办呢?可以使用mutableListOf()函数
val list = mutableListOf("apple", "orange", "grape", "pear", "banana")
list.add("watermelon")
for (fruit in list) {
println(fruit)
}
mutableListOf()函数用于创建一个可变集合。
同样的,对于Set集合同样存在 setOf() 和 mutableSetOf() 函数,用法类似。
我们再来看一下Map的用法。
按照传统方式我们通常是这样用的:
val map = HashMap<String, Int>()
map.put("apple",1)
但在kotlin中并不建议使用put()和get()方法来对Map进行添加和读取操作,而是更加推荐使用一种类似于数组下标的语法结构。向Map添加一条数据可以这样写:
map["apple"] = 1
而读取数据可以这样写:
val num=map["apple"]
同样的,Kotlin中也对Map提供了mapOf() 和 mutableMapOf() 函数来简化Map的初始化,我们可以直接在Map初始化的时候传入键值对组合:
val map= mapOf("a" to 1, "b" to 2, "c" to 3, "d" to 4)
for ((letter, num) in map) {
println("letter is $s,num is $num")
}
在上述代码的for-in循环中,我们将Map的键值对变量一起声明到了一堆括号中,这样每次进行循环遍历时,便会将遍历结果赋值给这两个键值对变量,然后打印出来。
集合的函数式API有很多个,这里我们重点学习函数式API的语法结构(即lambda表达式语法结构)。
需求:找出水果集合中单词最长的水果
val list = listOf("apple", "banana", "orange", "pear", "grape", "watermelon")
//1).传统写法
var maxLenthFruit=""
for (fruit in list) {
if (fruit.length>maxLenthFruit.length){
maxLenthFruit=fruit
}
}
//2) 函数式Api写法
val maxLenthFruit=list.maxBy {it.length}
println("max length fruit is $maxLenthFruit")
maxBy() 是kotlin集合提供的自带函数,后面跟的{it.length}是Lambda表达式的简化写法。那什么是Lambda呢?简单的说 Lambda就是一小段可以作为参数传递的代码。Lambda表达式的语法结构如下:
{参数名1:参数类型,参数名2:参数类型 ->函数体}
首先最外层是一对大括号,如果有参数传入到Lambda表达式中的话,我们还需要声明参数列表,参数列表结尾使用->符号,表示参数列表的结束以及函数体的开始,函数体中可以编写任意行代码,并且最后一行代码会自动作为Lambda表达式的返回值。
在实际使用中,我们不需要使用Lambda表达式的完整语法结构,而是使用简化写法。下面我们就用“找出水果集合中单词最长的水果”的例子来一步步推导演化,加深理解。
我们首先来了解一下maxBy的工作原理,maxBy接收的是一个Lambda类型的参数,并且会在遍历集合的时将每次遍历的值作为参数传递给Lambda表达式,其工作原理是根据传入的条件来遍历集合,从而找到该条件下的最大值。那么套用Lambda表达式的语法结构我们就可以写出如下代码:
val list = listOf("apple", "banana", "orange", "pear", "grape", "watermelon")
val lambda={fruit:String -> fruit.length}
val maxLenthFruit=list.maxBy (lambda)
变量直接替换:
val maxLenthFruit=list.maxBy ({fruit:String -> fruit.length})
Kotlin规定:当Lambda参数是函数的最后一个参数时,可以将Lambda表达式移到函数括号的外面。
val maxLenthFruit=list.maxBy(){fruit:String -> fruit.length}
接下来,如果Lambda参数是函数的唯一一个参数的话,还可以将函数的括号省略。
val maxLenthFruit=list.maxBy {fruit:String -> fruit.length}
然后,由于kotlin拥有出色的类型推导机制,Lambda表达式中的参数在大多数情况下不必声明参数类型,所以进一步简化:
val maxLenthFruit=list.maxBy {fruit -> fruit.length}
最后,当Lambda表达式的参数列表中只有一个参数时,也不必声明参数名,而是可以直接使用 it关键字 代替
val maxLenthFruit=list.maxBy {it.length}
下面我们再来学习几个集合中比较常用的函数式API。
集合中的map函数是最常用的一种函数式API,它用于将集合中的每个元素都映射成另外一个值,映射的规则在Lambda表达式中指定,最终生成一个新的集合。例如,我们希望所有的水果名称都变成大写:
val list = listOf("apple", "banana", "orange", "pear", "grape", "watermelon")
val newList =list.map { it.toUpperCase() }
for (fruit in newList) {
print(fruit)
}
val list = listOf("apple", "banana", "orange", "pear", "grape", "watermelon")
val newList = list.filter { it.length<=5 }.map { it.toUpperCase() }
for (fruit in newList) {
println(fruit)
}
val list = listOf("apple", "banana", "orange", "pear", "grape", "watermelon")
val anyResult = list.any { it.length <= 5 }
val allResult = list.all { it.length <= 5 }
println("anyResult is $anyResult,allResult is $allResult,")
在kotlin中调用java方法时也可以使用函数式API,但有一定的条件限制。如果在kotlin中调用了一个java方法,并且该方法接收一个java单抽象方法接口参数,就可以使用函数式API。单抽象方法接口指的是接口中只有一个待实现方法,如果接口中有多个待实现方法,则无法使用函数式API。比如java中的Runnable接口就是一个单抽象方法。
new Thread(new Runnable(){
@Override
public void run(){
System.out.println("Thread is running")
}
}).start();
这是java中写法,如果把上述代码翻译成kotlin版本,写法如下:
Thread(object :Runnable{
override fun run() {
println("Thread is running")
}
}).start()
Kotlin中匿名类的写法和java有一点区别,由于kotlin完全舍弃了new关键字,因此创建匿名类实例就不能使用new了,而是改用object关键字。上述代码符合java函数式API的使用条件,所以可以简化为:
Thread(Runnable{
println("Thread is running")
}).start()
因为Runnable类中只有一个待实现方法,即使这里没有显式地重写run()方法,kotlin也能自动明白Runnable后面的Lambda表达式就是要在run()方法中实现的内容。
另外,如果一个java方法的参数列表中不存在一个以上java单抽象方法接口参数,我们还可以将接口名进行省略,进一步精简代码:
Thread({
println("Thread is running")
}).start()
你以为这就结束了?和之前kotlin中函数式API用法类似,当Lambda表达式是方法的最后一个参数时,可以将Lambda表达式移到方法括号的外面,同时,如果Lambda表达式是方法的唯一一个参数,还可以将方法的括号省略,最终简化结果如下:
Thread{
println("Thread is running")
}.start()