在写这篇文章之前,Kotlin的基础和进阶课程都已经学习完了。这里简单做一个总结:
Kotlin基础学习(一)主要知识点:Kotlin中的变量与函数,逻辑控制,类与对象
Kotlin基础学习(二)主要知识点:集合的创建与遍历,Lambda编程
Kotlin基础学习(三)主要知识点:空指针检查,Kotlin中的简单特性
Kotlin进阶学习(四) 主要知识点:标准函数,静态方法,变态延迟初始化与密封类
Kotlin进阶学习(五)主要知识点:扩展函数,运算符重载,高阶函数,内联函数
Kotlin进阶学习(六)主要知识点:高阶函数的应用,泛型基础,infix函数
Kotlin进阶学习(七)主要知识点:泛型高级,委托
Kotlin进阶学习(八)主要知识点:协程的内容
要学习Lambda编程,集合的函数式API接口是入门Lambda的最佳案例,不过我们要先学习Kotlin中的集合。
集合,对于熟悉java的人来说,不用多说了。传统意义上的集合主要指List和Set,如果广泛点说的话Map这样的键值对结构也可以说是集合。在java里,List,Set,Map都是接口,我们一般都用他们的实现类,如ArrayList,HashSet,HashMap等等。
现在,为了入门集合,我们先提出这样一个需求:创建一个包含多个水果名称的集合。放到java里,相信我们都会写,kotlin中自然也能这么写:
val list = ArrayList()
list.add("Apple")
list.add("Banana")
list.add("Orange")
list.add("Pear")
list.add("Grape")
但这种写法未免太过于繁琐了。Kotlin为此提供了一个内置的listOf()方法来简化这种初始化的写法:
val list = listOf("Apple", "Banana", "Orange", "Pear","Grape")
可以看到,我们只用了一行代码就完成了集合的初始化操作。
之前我们学习了for-in循环,for-in循环不仅可以遍历区间,也可以遍历集合,如下:
for (fruit in list) {
println(fruit)
}
我们输出,发现输出了我们想要的结果:
不过这里需要注意,listOf()函数创建的是一个不可变的集合,什么叫不可变的集合呢?其实就是只能读,不能增删改。我们从这里也可以看出kotlin对于这些不可变性控制的十分严格。那我们怎么创建一个可变的集合呢?使用mutableListOf()函数就可以了:
val list = mutableListOf("Apple", "Banana", "Orange", "Pear","Grape")
list.add("WaterMelon")
for (fruit in list) {
println(fruit
}
这里我们添加了一个新的元素西瓜,我们运行看看结果:
与我们预期的一致。
而对于Set,其实和List的用法是几乎一模一样的,只是将用的函数变成了setOf()和mutableSetOf()而已,这里就不再赘述了。
我们在java中要创建一个Map要怎么做呢?很简单,new一个HashMap,定义好键和值的类型,然后挨个put就是了,在kotlin中也是一样的:
val map = HashMap()
map.put("Apple",1)
...
但其实在kotlin中不建议我们使用put()和get()方法来对Map进行添加和读取操作。而是更加推荐一种类似数组下标的语法结构,如下:
map["Apple"] = 1val number = map["Apple"]
如果学过php的话,会发现与php中的关联数组的用法似乎很类似呢。
当然,这种写法仍然很麻烦。那么kotlin中有我们上面用的listOf或者setOf()方法吗?显然是有的:
val map2 = mapOf("Apple" to 1, "Banana" to 2, "Orange" to 3)
这里看上去是用to关键字进行关联的,但实际上to并不是一个关键字,而是一个infix函数。这里暂时不再细究了。
最后,我们要遍历一个Map集合呢?当然也可以使用for-in了,如下:
val map2 = mapOf("Apple" to 1, "Banana" to 2, "Orange" to 3)
for ((fruit,number) in map2) {
println("fruit is " + fruit + ", number is " + number)
}
这里与刚才的区别很明显,我们直接将键和值分别取出来,赋值给了fruit和number,最后进行了一个打印输出。如果学过ES6的语法的话,会发现也很类似。看来Kotlin吸取了很多脚本语言的特性啊。
想也知道,函数式API多的一批。这里我们就简单了解一些简单的函数式API,主要是为了学习Lambda表达式的语法结构。
我们先提出一个需求:如何在一个水果集合里面找到单词最长的那个水果呢?要实现这个需求我们很自然的会想到这样的一种写法:对list集合进行遍历,使用if语句挑选出长度最长的那个水果单词即可。这段代码我就不在这里写了,相信各位都能十分容易地写出这样的代码。下面我们来看看使用kotlin的函数式编程API写法怎么写呢?如下:
val list = listOf("Apple", "Banana", "Orange", "Pear","Grape")
val maxLengthFruit = list.maxBy { it.length }
可以看到,我们一行代码就找到了最长的那个水果单词,但可能还无法理解这段代码。下面我们来一点点的看。
Lambda编程的定义,就是一小段可以作为参数传递的代码,这个一小段代码指的是什么呢?kotlin并没有限制,但我们使用时最好不要写太长的代码,毕竟是参数嘛。
那么我们直接来看Lambda表达式的语法结构:
{参数名1:参数类型, 参数名2:参数类型 -> 函数体}
首先,最外层是一对大括号,如果有参数传入到Lambda表达式的话,我们就需要声明参数列表,在参数列表的尾部加一个-> 然后在函数体里写我们需要写的代码就好了。最后一行代码会自动作为Lambda表达式的返回值
当然,我们很多时候不会写这么标准的语法结构,大部分时候我们都使用的简化形式。下面我们一步一步把代码简化到入门介绍里的那一行代码。
仔细观察入门介绍里的一行代码:
val maxLengthFruit = list.maxBy { it.length }
这里使用了一个maxBy函数,maxBy函数其实就是一个普通的函数,但需要传入一个lambda表达式。maxBy的工作原理就是根据我们传入的条件来遍历集合,然后找到最大值。比如我们想找单词最长的值,传入单词的长度即可。
理解了maxBy函数后,我们就可以按照上面的格式写一个标准的Lambda表达式:
val list = listOf("Apple", "Banana", "Orange", "Pear","Grape")
val lambda = {fruit:String -> fruit.length}
val maxLengthFruit = list.maxBy(lambda)
这段代码很好懂吧?我们定义了一个fruit变量,然后把他的长度返回到了lambda变量,将其作为参数传给了maxBy函数。接下来我们开始简化。
显而易见的,我们并不需要单独定义一个lambda变量:
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}
可以看到,我们一步一步简化到了入门介绍里的一行代码。
学习完了Lambda表达式,我们就学几个最常用的函数式API来巩固一下吧。
首先来学习map函数。集合中的map函数是一种最常用的函数式API,可以把集合中的元素映射成另外的值,最终形成新的集合。比如我们希望将所有的水果名变成大写:
val list = listOf("Apple", "Banana", "Orange", "Pear","Grape")
val newList = list.map { it.toUpperCase() }
map函数十分强大,可以做很多事。接下来我们学习另一个函数式API——filter函数,filter函数听名字就知道了,是用来过滤集合中的元素的,可以单独使用也可以配合map函数。
比如我想保留五个5个字母以内的水果,且都变成大写:
val list = listOf("Apple", "Banana", "Orange", "Pear","Grape")
val newList = list.filter { it.length <= 5 }
.map { it.toUpperCase() }
最后,我们学习一下any和all函数,any用于判断集合中是否至少存在一个元素满足条件,all表示判断集合中是否所有元素都满足条件:
val anyResult = list.any{it.length <= 5}
val allResult = list.all{it.length <= 5}
其中的list指上文的list。返回值是布尔类型的true或false。
刚才我们学习了很多,但好像和安卓开发都没有什么关系。接下来我们学习一下Java函数式API的使用,学习了这个特性后会极大的便利我们的安卓开发。
kotlin实际上也可以使用函数式API,但有一定的条件限制:如果我们在Koltin中调用了一个java方法,并且该方法接收一个Java单抽象方法接口参数,就可以使用函数式API。Java单抽象方法接口指的是只有一个待实现的方法。
听起来似乎很抽象,接下来我们还是通过代码学习:
Thread(object:Runnable{
overridefunrun(){
println("Thread is running")
}
}).start()
由于Kotlin中没有new关键字,固创建匿名内部类就不能再使用new了,改用了object关键字。这样写虽然不是很复杂,但好像跟java的匿名类写法没啥区别。
但Thread类的构造方法此时就符合Java函数式API的使用条件,我们就直接对代码进行精简:
Thread(Runnable{
println("Thread is running")
})
可以看到,已经很方便了。但这里提出一个特性——如果一个Java方法的参数列表不存在一个以上Java单抽象方法接口参数,就可以将接口名进行省略:
Thread{
println("Thread is running")
}.start()
这样,代码就十分简单了。可这和我们做安卓开发有什么关系呢?要知道Android SDK里面还是使用Java编写的,有很多接口都具有这种特性。比如我们最常用的按钮的点击事件:
button1.setOnClickListener {}
就可以直接这么写了,还是方便了很多的。