面向表达式编程
通俗地理解,表达式就是可以返回值的语句。
kotlin
1 // 单纯的字面量表达式,值为1
-1 // 增加前缀操作符,值为-1
1+1 // 加法操作符,返回2
listOf(1,2,3) // 列表表达式
"kotlin".length // 值为6
这些都是非常明显的表达式。以下是Kotlin中更复杂的表达式例子:
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
void ifStatement(Boolean flag) {
String a = null;
if (flag) {
a = "dive into kotlin";
}
System.out.println(a.toUpperCase());
}
在Java中,由于if
是语句而不是表达式,因此这里必须对变量a
进行声明,这段代码潜在的问题是:a
必须在if
语句外进行声明并初始化为null
,假设flag
条件永远为true
,那么程序运行不会报错,否则运行时将会抛出空指针异常(即使编译会通过)。这就是它的副作用。
而在下面的Kotlin版本中,使用if
作为表达式就不会有这个问题:
kotlin
fun ifExpression(flag: Boolean) {
val a = if (flag) "dive into Kotlin" else ""
println(a.toUpperCase())
}
在if
作为表达式时,else
分支也必须被考虑,这很容易理解,因为表达式具备类型信息,最终它的类型就是if
、else
多个分支类型的相同类型或公共父类型。可以看出,基于表达式的方案彻底消除了副作用 ,让程序变得更加安全。与 Java 的函数不同,Kotlin 中所有的函数调用也都是表达式。
Unit类型:让函数调用皆为表达式
为什么说 Java 中的函数不都是表达式,因为存在特例void
,因为void
函数没有返回值类型,它就不能算作一个表达式。
那么,Kotlin 为什么要引入 Unit
呢?一个很大的原因是函数式编程侧重于组合,尤其是很多高阶函数,在源码实现的时候都是采用泛型来实现的。然而void
在涉及泛型的情况下会存在问题。
复合表达式:更好的表达力
相比语句而言,表达式更倾向于自成⼀块,避免与上下文共享状态,互相依赖,因此我们可以说它具备更好的隔离性 。隔离性意味着杜绝了副作用,因此我们用表达式描述逻辑可以更加安全。此外,表达式通常也具有更好的表达能力。
典型的⼀个例子就是表达式更容易进行组合。由于每个表达式都具有值,并且也可以将另⼀个表达式作为组成其自身的⼀部分,所以我们可以写出⼀个复合的表达式。
kotlin
val res: Int? = try {
if (result.success) {
jsonDecode(result.response)
} else null
} catch (e: JsonDecodeException) {
null
}
这个程序描述了获取一个HTTP响应结果,然后进行 json
解码,最终赋值给 res
变量的过程。它向我们展示了 Kotlin 如何利用多个表达式组合表达的能力:
try
在 Kotlin 中也是一个表达式,try/catch/finally
语法的返回值类型由try
或catch
部分决定,finally
不会产生影响;- 在 Kotlin 中,
if-else
很大程度上代替了传统三元运算符的做法,虽然增加了语法词数量,但是减少了概念,同时更利于阅读; if-else
的返回值即try
部分的返回值,最终res
的值由try
或catch
部分决定。
Kotlin 中的 "?:"
虽然Kotlin没有采用三元运算符,然而它存在⼀个很像的语法" ?:
"。注意,这里的问号和冒号必须放在⼀起使用,它被叫作 Elvis 运算符,或者 null 合并运算符。由于 Kotlin 可以用"?
"来表示一种类型的可空性,我们可以用" ?:
"来给一种可空类型的变量指定为空情况下的值。你可以通过以下的例子理解 Elvis 运算符:
kotlin
val maybelnt: Int? = null
maybelnt ?: 1 // 1
枚举类和 when 表达式
枚举是类
在Kotlin中,枚举是通过⼀个枚举类来实现的。
kotlin
enum class Day {
MON, TUE, WED, THU, FRI, SAT, SUN
}
跟Java相比写法上只多了一个calss
关键字,但与 Java 的枚举不同的是,由于它是⼀种类,它可以拥有构造参数,以及定义额外的属性和方法。
kotlin
enum class DayOfWeek(val day: Int) {
MON(1),
TUE(2),
WED(3),
THU(4),
FRI(5),
SAT(6),
SUN(7);
// 如果以下有额外的方法或属性定义,则必须强制加上分号
fun getDayNumber(): Int {
return day
}
}
用 when 来代替 if-else
kotlin
fun schedule(sunny: Boolean, day: Day) = when (day) {
Day.SAT -> basketball()
Day.SUN -> fishing()
Day.FRI -> appointment()
else -> when {
sunny -> library()
else -> study()
}
}
when
表达式的具体语法:
- 1)一个完整的
when
表达式类似switch
语句,由when
关键字开始,用花括号包含多个逻辑分支,每个分支由->
连接,不再需要switch
的break
(这真是⼀个恼人的关键字),由上到下匹配,一直匹配完为止,否则执行else
分支的逻辑,类似switch
的default
; - 2)每个逻辑分支具有返回值,最终整个
when
表达式的返回类型就是所有分支相同的返回类型,或公共的父类型。 - 3)
when
关键字的参数可以省略,如上述代码中的子when
表达式(但是这种情况下->
左侧的必须返回布尔值,否则编译会报错) - 4)表达式可以组合,所以这是⼀个典型的
when
表达式组合的例子。你在 Java 中很少见过这么长的表达式,但是这在 Kotlin 中很常见。
when
表达式还可以使用更加灵活的方式来解决嵌套深的问题:
kotlin
fun schedule(sunny: Boolean, day: Day) = when {
day == Day.SAT -> basketball()
day == Day.SUN -> fishing()
day == Day.FRI -> appointment()
sunny -> library()
else -> study()
}
再次强调,省略when()
中的参数时,每个分支的左侧必须是一个返回Boolean
类型的表达式。
for 循环和范围表达式
kotlin
for (i in 1..10) println(i)
与下面写法是等价的:
kotlin
for (i: Int in 1..10) {
println(i)
}
事实上,任何提供迭代器(iterator)的结构都可以用for-in
语句进行迭代,如:
kotlin
for (c in array) {
println(c)
}
此外,我们还可以通过调用⼀个withIndex
方法,提供一个键值元组:
kotlin
for ((index, value) in array.withlndex()) {
println("the element at $index is $value")
}
范围表达式
Kotlin官网中介绍:Range
表达式是通过rangeTo
函数实现的,通过"..
"操作符与某种类型的对象组成,除了整型的基本类型之外,该类型需实现java.lang.Comparable
接口。
举个例子,由于String
类实现了Comparable
接口,字符串值之间可以比较大小,所以我们就可以创建一个字符串区间,如:
kotlin
"abc".."xyz"
另外,当对整数 进行for
循环时,Kotlin 还提供了一个 step
函数来定义迭代的步长:
kotlin
for (i in 1..10 step 2) {
print(i)
}
>>> 13579
倒序方式:
kotlin
for (i in 10 downTo 1 step 2) { // 通过 downTo,而不是 10..1
print(i)
}
>>> 108642
此外,还有⼀个until
函数来实现⼀个半开区间:
kotlin
for (i in 1 until 10) {
print(i)
}
>>> 123456789 // 并不包含 10
用 in 来检查成员关系
在Kotlin中我们可以用in
来检查一个元素是否是一个区间或集合中的成员。
kotlin
"a" in listOf("b", "c")
>>> false
如果我们在in
前面加上感叹号,那么就是相反的判断结果:
kotlin
"a" !in listOf("b", "c")
>>> true
除了等和不等,in
还可以结合范围表达式来表示更多的含义:
kotlin
"kot" in "abc".."xyz"
>>> true
以上的代码等价于:
kotlin
"kot" >= "abc" && "kot" <= "xyz"
中缀表达式
一个中缀函数的表达形式非常简单,我们可以理解成这样:
kotlin
A 中缀方法 B
如果我们要定义一个中缀函数,它必须需满足以下条件:
- 该中缀函数必须是某个类型的扩展函数或者成员方法;
- 该中缀函数只能有一个参数;
- 中缀函数的参数不能有默认值(虽然Kotlin的函数参数支持默认值),否则以上形式的 B 会缺失,从而对中缀表达式的语义造成破坏;
- 该参数也不能是可变参数,因为我们需要保持参数数量始终为 1 个。
来看看 Kotlin 标准库中采用中缀表达式设计的to
方法,这是一个通过泛型实现的方法,可以返回一个Pair
。
kotlin
infix fun <A, B> A.to(that: B): Pair<A, B>
由于to
会返回Pair
这种键值对的结构数据,因此我们经常会把它与map
结合在一起使用。如以下例子:
kotlin
mapOf(
1 to "one",
2 to "two",
3 to "three"
)
自定义一个中缀表达式:
kotlin
class Person {
infix fun called(name: String) {
println("My name is $name.")
}
}
fun main() {
val p = Person()
p called "Shaw" // 输出:My name is Shaw.
}
也可以按照传统方式调用它:
kotlin
p.called("Shaw")