Kotlin 智能类型转换与 when 表达式(八)
导读大纲
- 1.0.1 智能转换: 结合类型检查和类型转换
- 1.0.2 重构: 用 when 表达式替换 if 表达式
- 1.0.3 块作为 if 和 when 的分支
- when 表达式专题系列
- 从枚举类引出 when 表达式
- 多种形式的 when 表达式
1.0.1 智能转换: 结合类型检查和类型转换
-
将编写一个函数来计算简单的算术表达式,比如 (1 + 2) + 4
- 这些表达式只包含一种类型的运算: 两个数字之和
- 其他算术运算(如减法、乘法和除法)也可以用类似的方法实现
- 在此过程中,您将了解智能转换
- 如何让处理不同类型的 Kotlin 对象变得更加容易
- 这些表达式只包含一种类型的运算: 两个数字之和
-
首先,如何对表达式进行编码?
- 传统的做法是将表达式存储在树状结构中
- 其中每个节点要么是一个和(Sum),要么是一个数(Num)
- Num 总是叶子节点,而Sum节点有两个子节点: 求和操作的参数
- <1> 用于编码表达式的简单类结构
- 一个名为 Expr 的接口, 没有声明任何方法
- 它被用作一个标记接口,为不同类型的表达式提供通用类型
- 实现该接口的两个类 Num 和 Sum
- 一个名为 Expr 的接口, 没有声明任何方法
- <2> 要标记一个类实现一个接口
- 可以在类的主构造器后使用冒号(:)+接口名
- Num 类实现 Expr 接口并带有一个Int类型的属性
- Sum 类也实现 Expr 接口并带有 left 和 right 两个Expr类型的属性
- 这里意味着它们可以是 Num 或 Sum
- 这里意味着它们可以是 Num 或 Sum
- 传统的做法是将表达式存储在树状结构中
interface Expr // <1>
class Num(val value: Int) : Expr // <2>
class Sum(val left: Expr, val right: Expr) : Expr // <2>
-
比如要存储前面提到的表达式 (1 + 2) + 4
- 需要创建一个 Expr 对象结构,具体为 Sum(Sum(Num(1), Num(2)),Num(4))
- 下图显示该结构的树形表示
- 目标是计算这种由 Sum 和 Num 对象组成的表达式
- 目标是计算这种由 Sum 和 Num 对象组成的表达式
-
Expr 接口有两个实现,因此你必须尝试两个选项来评估表达式的结果值
- 如果表达式是数字(Num),则返回相应的值
- 如果是求和(Sum),则必须递归评估左右表达式,并返回它们的和
-
首先,我们将查看该函数的实现,其编写风格类似于 Java 代码
- 然后,我们将对其进行重构,以反映 Kotlin的本土风格
- 最初,您可能会编写一个类似于其他语言风格的函数
- 使用一系列 if 表达式来检查 Expr 的不同子类型
- <1> 在 Kotlin 中,您可以使用 is 检查来检查变量是否属于某种类型
- 如果您使用 C# 编程,可能会对 is 语法感到熟悉
- Java 开发人员可能会将其等同于 instanceof
- 如果您使用 C# 编程,可能会对 is 语法感到熟悉
- <2> 智能转换将类型检查和类型转换合并为一个操作
- 这里
使用"as"
语法对Num类型的转换是多余的
- 这里
fun eval(e: Expr): Int {
if (e is Num) { // <1>
val n = e as Num // <2>
return n.value
}
if (e is Sum) { // <1>
return eval(e.right) + eval(e.left)
}
throw IllegalArgumentException("Unknown expression")
}
fun main() {
println(eval(Sum(Sum(Num(1), Num(2)), Num(4))))
// 7
}
- Kotlin 的 "is"检查提供一些额外的便利
- 如果你检查变量的特定类型,那么之后你就不需要再对它进行显式转换
- 你可以把它当作已检查过的类型来使用
- 实际上,编译器会为你执行类型转换,我们称之为智能类型转换
- 在 eval 函数中,在检查变量 e 是否具有 Num 类型后
- 编译器会将其智能地解释为 Num 类型的变量
- 然后,您就可以访问 Num 类型的属性,而无需进行显式转换
- 在IntelliJ IDEA和Android Studio中,这些智能转换的值会用背景色凸显
- 因此很容易掌握该值是否事先经过检查
- 同时也可以看到下图中"as Num"背景是灰色, 表示多余
- 如果你检查变量的特定类型,那么之后你就不需要再对它进行显式转换
1.0.2 重构: 用 when 表达式替换 if 表达式
- 在前面的学习中,我们已经知道 if 在 Kotlin 中是一个表达式
- 这也是 Kotlin 中没有三元操作符的原因–if 表达式可以返回一个值
- <1> 这意味着你可以重写 eval 函数,使用表达式体语法
- 去掉 return 语句和大括号, 使用 if 表达式作为函数体
fun eval(e: Expr): Int =
if (e is Num) { // <1>
e.value // <1>
} else if (e is Sum) {
eval(e.right) + eval(e.left)
}else {
throw IllegalArgumentException("Unknown expression")
}
- 你还可以让这段代码更加简洁
- 如果 if 分支中只有一个表达式,那么大括号是可选的
- 如果一个if分支中有一个代码块,那么最后一个表达式将作为结果返回
fun eval(e: Expr): Int =
if (e is Num) e.value
else if (e is Sum) eval(e.right) + eval(e.left)
else throw IllegalArgumentException("Unknown expression")
- 使用 when 重写它,这里我们使用不同形式的 when 分支
- 允许我们检查 when 参数值的类型
- <1> when分支对参数类型进行检查和智能转换
- 第一个分支检查通过,则认为e是Num类型,即可以直接访问Num类型的属性
- 同上,可以直接访问Sum类型的属性,这就是智能转换的强大之处
fun eval(e: Expr): Int =
when(e) {
is Num -> e.value // <1>
is Sum -> eval(e.left) + eval(e.right) // <1>
else -> throw IllegalArgumentException("Unknown expression")
}
1.0.3 块作为 if 和 when 的分支
-
if 和 when 都可以将代码块作为分支
- 在这种情况下,块中的最后一个表达式就是结果
-
假设你想深入了解 eval 函数是如何计算结果的
- 一种方法是添加 println 语句,记录函数当前正在计算的内容
- <1> 你可以为 when 表达式中的每个分支在代码块中添加println语句
- 代码块中的最后一个表达式将作为结果返回
- 在 is Num 分支, e.value 表达式作为结果返回
- 在 is Sum 分支, left + right 作为结果返回
fun eval(e: Expr): Int =
when(e) {
is Num -> {
println("num: ${e.value}")
e.value // <1>
}
is Sum -> {
val left = eval(e.left)
val right = eval(e.right)
println("sum: $left + $right")
left + right // <1>
}
else -> throw IllegalArgumentException("Unknown expression")
}
- 代码块中的最后一个表达式即为结果的规则
- 适用于所有可以使用代码块并期望得到结果的情况
- 比如上面示例中的 when 分支
- 在之后学到的异常捕获中,这条规则同样适用于try body和catch子句
- 也将讨论这条规则在 lambda 表达式中的应用
- 但正如之前提到的, 这条规则并不适用于常规的函数
- 函数的代码块体必须显式使用 return 语句
- 代码块形式的函数与表达式形式的函数
- 适用于所有可以使用代码块并期望得到结果的情况