4.面向表达式编程

Smile_slime_47

Kotlin支持将一系列的流程控制语句如if-else、try、when作为表达式使用,并返回一个值,相比离散的代码块,表达式赋值让代码更加紧凑,也更加安全,大部分函数式编程的语言如Scala、F#等,都采用了一切皆表达式的设计理念

Unit类型

Java原生不支持函数式编程的主要原因是函数的返回类型支持void,在函数式编程中,一个表达式/函数是必须有一个返回类型的,Java中的null作为无意义的指示符,但是null并不代表一个实例对象

当我们试图在Java中手动定义函数时:

1
2
3
interface Function<Arg,Return>{
Return apply(Arg arg)
}

考虑到void类型方法,如print,我们不得不用void的封装类型Void,由于Void类型不允许实例,我们不得不返回一个null

1
2
3
4
5
6
Function<String,Void> print=new Function<String,Void>(){
public Void apply(String arg){
sout(arg);
return null;
}
}

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
2
3
4
5
6
7
val res: Int? = try{
if(result.success) {
jsonDecode(result.response)
} else null
} catch (e: JsonDecodeException) {
null
}

Elvis运算符

在开发中我们经常需要判断对象是否为null,在没有三元运算符的情况下会稍微麻烦,可以通过:?Elvis运算符(也叫做nulll合并运算符 )实现效果,要注意的是,:和?是必须连接在一起的,这和三元运算符的逻辑是不一样的

1
2
3
4
int a=(b!=null?b:0)+1               //Java写法

val a=(if (b!=null) b else 0)+1 //if-else表达式
val a=(b?:0)+1 //Elvis运算符

枚举类

对于一个简单的例子

1
2
3
4
5
6
7
8
9
enum calss Day{
MON,
TUE,
WEN,
THU,
FRI,
SAT,
SUN
}

一个复杂些的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
enum calss Day(val day:Int){    //主要构造函数,定义了类的成员变量
MON(1), //枚举选项通过逗号分隔
TUE(2),
WEN(3),
THU(4),
FRI(5),
SAT(6),
SUN(7)

; //枚举选项和额外的函数/属性定义之间用;分割

fun getDay():Int{
return day
}
}

when表达式

1
2
3
4
5
6
7
8
9
fun schedule(sunny:Boolean,day:Day) = when(day){
Day.SAT -> basketball()
Day.SUN -> fishing()
Day.FRI -> appointment()
else -> when{
sunny -> libray()
else -> study(
}
}

我们可以发现一些when表达式的一些特性:

  • 每个分支通过->连接条件和表达式,不需要break关键字,通过else关键字执行默认分支
  • when表达式会返回所有分支的最近公共父类
  • 在表达式仅包含一个Boolean条件的情况下,可以省去when右侧的括号

此外,when也可以替代复杂的if-else嵌套语句,并基于从上到下的匹配原则,对于上例我们可以改写为这种形式:

1
2
3
4
5
6
7
fun schedule(sunny:Boolean,day:Day) = when{
day==Day.SAT -> basketball()
day==Day.SUN -> fishing()
day==Day.FRI -> appointment()
sunny -> libray()
else -> study(
}

范围表达式

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
2
print("a" in listOf("a","b","c")) //true
print("a" !in listOf("a","b","c")) //false

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
2
fun Int.sum(that:Int):Int=this+that
print(1.sum(2)) //3

这里的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
2
3
4
5
mapOf{
1 to "one"
2 to "two"
3 to "three"
}

以下是一个不使用泛型的更简单的例子

1
2
infix fun Int.sum(that:Int):Int=this+that
print(1 sum 2 sum 3) //6
Comments