目录
五 Lambda编程
数组(Array)
使用arrayOf()来创建参数是一个可变参数的泛型对象。如下所示
val arr1= arrayOf(1,2,3,4,5)
val arr2= arrayOf("0","1","2",32.4f)
使用arrayOfNulls()用于创建一个指定数据类型且可以为空的给定元素的个数的数组。如下所示
val arr3 = arrayOfNulls<Int>(3)
除了以上方法,kotlin提供了专门的类来表示原始类型的数组。它们分别是
ByteArray | 字节型数组 |
ShortArray | 短整型数组 |
IntArray | 整形数组 |
LongArray | 长整型数组 |
BooleanArray | 布尔型数组 |
CharArray | 字符型数组 |
FloatArray | 浮点型数组 |
DobleArray | 双精度浮点型数组 |
kotlin中不支持字符串类型这种原始型数组。例子如下
val arr:IntArray= intArrayOf(1,2,3,4,5)
集合
List
val intList = listOf<Int>(1,2,3)//声明一个不可变的Int型List
val stringList= mutableListOf<String>("1","2","3")//声明一个可变的String的List
其中不可变的意思是该集合只能用于读取。
Set
val intList = setOf<Int>(1,2,3)//声明一个不可变的Int型Set
val stringList= mutableSetOf<String>("1","2","3")//声明一个可变的String的Set
Map
val map = mapOf<String,Int>("1" to 1,"2" to 2,"3" to 3)//声明一个不可变的map
val mutableMap = mutableMapOf<String,Int>("1" to 1,"2" to 2,"3" to 3)//声明一个可变的map
在对Map进行操作的时候,推荐使用类似于数组下标的方式进行操作数据,如map["1"]=1
集合的函数式API
val list= listOf("Apple","Banana","Orange","Pear")
val maxLength= list.maxByOrNull { it.length }
上述代码表示找到集合中单词最长的元素。其中使用了集合的函数式API的写法。首先我们了解一下Lambda表达式的定义,其实它就是一小段可以作为参数传递的代码,它的语法结构为
{参数名1:参数类型,参数名2:参数类型 ->函数体}
最后一行代码会成为Lambda表达式的返回值。
于是刚刚那段代码的简化过程其实是这样的
var maxLength= list.maxByOrNull { fruit: String -> fruit.length }
maxLength=list.maxByOrNull { fruit->fruit.length }
maxLength=list.maxByOrNull { it.length }
如果希望让所有的元素都变成大写,就可以使用如下API
val newList=list.map { it.uppercase(Locale.getDefault()) }
比较常用的API——filter函数,起到过滤元素的作用
val newList=list.filter { it.length<=5 }.map { it.uppercase(Locale.getDefault()) }
比较常用的API——any函数,用于判断集合中是否至少存在一个元素满足指定条件。
val anyResult=list.any { it.length<=5 }
比较常用的API——all函数,用于判断集合中是否所有元素都满足指定条件。
val allResult=list.all { it.length<=5 }
上面我们讨论了kotlin中函数式API的用法,但实际上在kotlin调用Java方法的时候,也可以使用函数式API。具体来说,如果在kotlin代码中调用了一个Java方法,并且该方法接受一个Java单抽象方法接口参数,就可以使用函数式API。
Java原生API中有一个最为常见的单抽象方法接口参数——Runnable接口。这个接口中只有一个待实现的run()方法。
public interface Runnable {
void run();
}
根据前面所说,对于任何Java方法,只要它接收Runnable参数,就可以使用函数式API,其实有很多方法接受了Runnable参数,但是Runnable主要还是结合线程一起使用的,因此这里我们通过线程类Thread来学习。Thread类的构造方法中接收了一个Runnable参数,可以使用如下代码创建一个子线程:
new Thread(new java.lang.Runnable() {
@Override
public void run() {
System.out.println("Thread is running");
}
}).start();
这里使用了匿名类的写法,创建了一个Runnable接口的匿名类实例,并传入Thread类的构造方法,最后调用start方法执行这个线程。
但是如果采用kotlin来实现这段代码,只需要如下
Thread{
println("Thread is running")
}.start()
由于Runnable类中只有一个待实现方法,即使没有显式的重写run方法,kotlin也明白Runnable后面的Lambda表达式就是run方法中实现的内容。另外,如果Java方法参数列表中有且仅有一个Java单抽象方法接口参数,还可以把接口名省略,紧接着,如果Lambda表达式还是方法的唯一一个参数,还可以将方法的括号省略。
空指针检查
Java中的空指针异常只能通过程序员通过逻辑避免,在Kotlin中,它利用编译时判空检查的机制几乎杜绝了空指针异常。
Kotlin默认所有的参数和变量都不可为空,如果试图传入一个null参数,那么在编译时是不会通过的。但是如果我们的需求需要某个参数或者变量为空怎么办呢,Kotlin提供了一套可为空的类型系统,但是在使用可为空的类型系统的时候,我们需要在编译时间就将所有潜在的空指针异常处理掉,否则代码不会编译通过。
可空类型系统就是在类名后面加上问号,比如Int表示不可为空的整形,Int?表示可以为空的整形,String表示不可为空的字符串,而String?表示可为空的字符串,
fun doStudy(study:Study?){
if (study != null) {
study.doHomework()
study.read()
}
}
如果希望传入的参数可以为空,那么将参数类型后面加上?,然后再对参数进行判空逻辑操作,即可避免空指针异常。但是为了在编译时期处理掉所有的空指针异常,我们需要编写很多检查代码,如果每一处都使用if判断语句的话,则会让代码比较啰嗦,而且if还处理不了全局变量的判空问题。
为此,Kotlin提供了一系列的辅助工具。
判空辅助工具
首先是?.操作符,表示当对象不为空的时候正常调用相应方法,当对象为空的时候什么都不做,
比如上一段代码可以写成如下样子
fun doStudy(study:Study?){
study?.doHomework()
study?.read()
}
还有?:操作符,这个操作符左右两边都接收一个表达式,如果左边表达式的结果不为空就返回左边表达式的结果,否则就返回右边表达式的结果。如果要编写一个函数来获得一段文本的长度,传统的写法如下
fun getTextLength(text:String?):Int{
if (text != null) {
return text.length
}
return 0
}
使用?:操作符简化如下
fun getTextLength(text:String?)=text?.length?:0
由于text是可能为空的,因此我们在调用length字段的时候要使用?.操作符,当text为空的时候,text?.length返回一个null,然后左边为空返回右边。
不过Kotlin的空指针检查也不总是那么智能,比如如下代码
var content:String?="hello"
fun main() {
if (content != null) {
printUpperCase()
}
}
fun printUpperCase(){
val upperCase=content.uppercase()
println(upperCase)
}
这段代码是不能通过编译的,因为在printUpperCase方法总认为存在空指针异常,但是事实上我们在main函数中已经做了非空检查。如果需要强行通过编译,则可以使用!!操作符,如下所示,
var content:String?="hello"
fun main() {
if (content != null) {
printUpperCase()
}
}
fun printUpperCase(){
val upperCase=content!!.uppercase()
println(upperCase)
}
但是这是一种有风险的写法,如果有更好的实现方式,最好不要使用它。
最后还有一个比较与众不同的辅助工具——let,let既不是操作符,也不是关键字,而是一个函数,这个函数提供了函数式API的编程接口,并将调用对象作为参数传递进Lambda表达式,示例如下
obj.let{obj2->
//
}
事实上obj2就是obj。
let函数的特性如果搭配?.操作符可以在空指针检查时起到很大作用,上文中?.操作符还可以进一步优化成下面这个样子
fun doStudy(study:Study?){
study?.let {stu->
stu.doHomework()
stu.read()
}
}
当study不为空的时候,调用let函数,把自己作为参数传递进去执行逻辑,此时对象肯定不为空。当Lambda参数列表只有一个参数时,代码可以进一步简化
fun doStudy(study:Study?){
study?.let {
it.doHomework()
it.read()
}
}
除此之外,let函数可以处理全局变量的判空问题,而if语句无法做到。
字符串内嵌表达式
Java是并不支持字符串内嵌的,相反的,Kotlin实现了这一功能。如下
fun getName(){
val name="Jack"
println("name is ${name}")
}
这种方式使得我们不用在去拼接字符串,一定程度上增加了开发的便利性。特别的,如果表达式中只有一个变量,可以将大括号省略,如下
fun getName(){
val name="Jack"
println("name is $name")
}