《Kotlin核心编程》 ——2.4 面向表达式编程
2.4 面向表达式编程
在本章之前的几节中,我们已经好几次与一个关键字打过交道,这就是“表达式”。现在罗列下我们已经提及的表达式概念:
if表达式
函数体表达式
Lambda表达式
函数引用表达式
显然,表达式在Kotlin这门语言中处于一个相当重要的地位,这一节我们会着重介绍在Kotlin中如何利用各种表达式来增强程序表达、流程控制的能力。与Java等语言稍显不同的是,Kotlin中的流程控制不再是清一色的普通语句,它们可以返回值,是一些崭新的表达式语句,如if表达式、when表达式、try表达式等。这样的设计自然与表达式自身的特质相关。在了解具体的语法之前,我们先来探究下表达式和普通语句之间的区别。
表达式(expressions)和语句(statements)虽然是很基本的概念,但也经常被混淆和误解。语句很容易理解,我们在一开始学习命令式编程的时候,程序往往是由一个个语句组成的。比如以下这个例子:
fun main(args: Array<String>) {
var a = 1
while (a < 10) {
println(a)
a++
}
}
可以看到,该程序依次进行了赋值、循环控制、打印等操作,这些都可以被称为语句。我们再来看看什么是表达式:
表达式可以是一个值、常量、变量、操作符、函数,或它们之间的组合,编程语言对其进行解释和计算,以求产生另一个值。
通俗地理解,表达式就是可以返回值的语句。我们来写几个表达式的例子:
1 // 单纯的字面量表达式,值为1
-1 // 增加前缀操作符,值为-1
1 + 1 // 加法操作符,返回2
listOf(1, 2, 3) //列表表达式
"kotlin".length // 值为6
这些都是非常明显的表达式。以下是Kotlin中更复杂的表达式例子:
{ x: Int -> x + 1 } // Lambda表达式,类型为(Int) -> Int
fun(x: Int) { println(x) } // 匿名函数表达式,类型为(Int) -> Unit
if ( x > 1) x else 1 // if-else表达式,类型为Int,假设x已赋值
正如我们所言,一些在其他语言中的普通语句,在Kotlin中也可以是表达式。这样设计到底有什么好处呢?
2.4.1 表达式比语句更安全
我们先来写一段Java代码。刚开始我们还是采用熟悉的if语句用法:
void ifStatement(Boolean flag) {
String a = null;
if (flag) {
a = "dive into kotlin";
}
System.out.println(a.toUpperCase());
}
非常简单的代码,由于if在这里不是一个表达式,所以我们只能够在外部对变量a进行声明。仔细思考一下,这段代码存在潜在的问题:
a必须在if语句外部声明,它被初始化为null。这里的if语句的作用就是对a进行赋值,这是一个副作用。在这个例子中,我们忽略了else分支,如果flag的条件判断永远为true,那么程序运行并不会出错;否则,将会出现“java.lang.NullPointerException”的错误,即使程序依旧会编译通过。因此,这种通过语句创建副作用的方式很容易引发bug。
继续思考,现在的逻辑虽然简单,然而如果变量a来自上下文其他更远的地方,那么这种危险会更加容易被忽视。典型的例子就是一段并发控制的程序,业务开发会变得非常不安全。
接下来,我们再来创建一个Kotlin的版本,现在if会被作为表达式来使用:
fun ifExpression(flag: Boolean) {
val a = if (flag) "dive into Kotlin" else ""
println(a.toUpperCase())
}
下面分析Kotlin的版本:
表达式让代码变得更加紧凑了。我们可以把赋值语句与if表达式混合使用,就不存在变量a没有初始值的情况。
在if作为表达式时,else分支也必须被考虑,这很容易理解,因为表达式具备类型信息,最终它的类型就是if、else多个分支类型的相同类型或公共父类型。
可以看出,基于表达式的方案彻底消除了副作用,让程序变得更加安全。当然,这并不是说表达式不会有副作用,实际上我们当然可以用表达式写出带有副作用的语句,就像这样子:
var a = 1
fun foo() = if (a > 0) {
a = 2 // 副作用,a的值变化了
a
} else 0
然而从设计角度而言,语句的作用就是服务于创建副作用的,相比较表达式的目的则是为了创造新值。在函数式编程中,原则上表达式是不允许包含副作用的。
一切皆表达式
撇开Haskell不谈,在一些极力支持函数式编程的语言中,比如Scala和F#,即使它们不是纯函数式语言,也都实现了一个特性,即一切皆表达式。一切皆表达式的设计让开发者在设计业务时,促进了避免创造副作用的逻辑设计,从而让程序变得更加安全。
由于把百分之百兼容Java作为设计目标,Kotlin并没有采纳一切皆表达式的设计,然而它在Java的基础上也在很大程度上增强了这一点。正如另一个接下来要提及的例子,就是Kotlin中的函数。与Java的函数不同,Kotlin中所有的函数调用也都是表达式。
- 点赞
- 收藏
- 关注作者
评论(0)