4.面向表达式编程
Kotlin支持将一系列的流程控制语句如if-else、try、when作为表达式使用,并返回一个值,相比离散的代码块,表达式赋值让代码更加紧凑,也更加安全,大部分函数式编程的语言如Scala、F#等,都采用了一切皆表达式的设计理念
Unit类型
Java原生不支持函数式编程的主要原因是函数的返回类型支持void,在函数式编程中,一个表达式/函数是必须有一个返回类型的,Java中的null作为无意义的指示符,但是null并不代表一个实例对象
当我们试图在Java中手动定义函数时:
1 | interface Function<Arg,Return>{ |
考虑到void类型方法,如print,我们不得不用void的封装类型Void,由于Void类型不允许实例,我们不得不返回一个null
1 | Function<String,Void> print=new Function<String,Void>(){ |
Kotlin引入了Unit类型,Unit类型是单例的,并且表示为()
,在我们调用Kotlin中无返回值的函数时,它实际上是返回了一个无意义的Unit对象
if-else表达式和try表达式
kotlin中不存在三元表达式,因为if-else表达式可以实现相同的效果,减少了概念的数量
1 | max = if(max < tmp) tmp else max |
同样地,try-catch-finally也可以作为表达式使用,这种情况下,返回值由try和catch部分决定
Kotlin同样支持表达式嵌套,如
1 | val res: Int? = try{ |
Elvis运算符
在开发中我们经常需要判断对象是否为null,在没有三元运算符的情况下会稍微麻烦,可以通过:?
即Elvis运算符(也叫做nulll合并运算符 )实现效果,要注意的是,:和?是必须连接在一起的,这和三元运算符的逻辑是不一样的
1 | int a=(b!=null?b:0)+1 //Java写法 |
枚举类
对于一个简单的例子
1 | enum calss Day{ |
一个复杂些的例子:
1 | enum calss Day(val day:Int){ //主要构造函数,定义了类的成员变量 |
when表达式
1 | fun schedule(sunny:Boolean,day:Day) = when(day){ |
我们可以发现一些when表达式的一些特性:
- 每个分支通过
->
连接条件和表达式,不需要break关键字,通过else关键字执行默认分支 - when表达式会返回所有分支的最近公共父类
- 在表达式仅包含一个Boolean条件的情况下,可以省去when右侧的括号
此外,when也可以替代复杂的if-else嵌套语句,并基于从上到下的匹配原则,对于上例我们可以改写为这种形式:
1 | fun schedule(sunny:Boolean,day:Day) = when{ |
范围表达式
Range表达式
- 闭区间[1,10]:
for(i in i..10)
- 逆序闭区间[10,1]:
for(i in 10 downTo 1)
- 半开区间[1,10):
for(i in 1 until 10)
- 设定步长:
for(i in 1..10 step 2)
对于任何实现了Comparable接口的类都可以创造一个范围表达式,例如"abc".."xyz"
,但是在此基础上只有提供了iterator的类才能通过此方式被for迭代
in关键字
我们可以通过in关键字判断一个元素是否在某个区间/集合/范围中,例如
1 | print("a" in listOf("a","b","c")) //true |
in关键字也可以用在范围表达式中,例如
1 | print("kot" in "abc".."xyz") //true |
当我们在for循环中使用in关键字时,会自动调用右值的iterator(因此要求该结构必须提供一个iterator)
1 | for(i in arr) |
对于线性表结构,还可以通过withIndex方法得到一个包含了元素下标和元素数据的键值对
1 | for((index,value) in arr.withIndex()) |
扩展函数
我们可以在类外为类补充新的函数,例如
1 | fun Int.sum(that:Int):Int=this+that |
这里的Int
叫做接收者类型
在传入类时,对于接收者的某些成员可以省略this,例如
1 | fun Book.getName():String = name//而非this.name |
中缀表达式
上面提到的until
、..
、in
是基于中缀表达式实现的,中缀表达式允许我们不通过.
来直接调用某个类的方法(尽管如此,我们仍然可以通过.
调用中缀函数),要声明一个中缀函数,需要满足以下条件
- 在fun前添加infix关键字
- 该函数必须为某个类的扩展函数或成员函数
- 该函数只能接受一个参数
- 该函数的默认值不能有默认值
以Kotlin的to为例
1 | infix fun<A,B> A.to(that :B):Pair<A,B> |
当我们输入A to B
时,实际上返回了一个<A,B>的Pair,这在map中非常常用:
1 | mapOf{ |
以下是一个不使用泛型的更简单的例子
1 | infix fun Int.sum(that:Int):Int=this+that |