在Java等传统编程语言中,处理复杂数据结构(如树)时,通常依赖getter方法访问内部属性。 然而,这些结构往往没有预定义所需的方法,且有时无法修改其代码来添加新方法。导致开发者必须手动逐层访问结构,代码变得冗长、易错、难以维护。
解决方案引入:模式匹配
模式匹配是一种更强大、简洁的数据解构方式,能够直接“匹配”并提取数据结构中的所需部分。
相比于Java的if-else或switch-case,Kotlin中的when表达式在多分支处理上更简洁优雅。
Kotlin中的实践与局限:
Kotlin并未实现“完整”的模式匹配(如函数式语言中的强大形式)。但结合 when 表达式、解构声明(Destructuring) 和 智能类型转换(Smart Casts),已能满足大多数工程需求。
这体现了Kotlin的实用主义设计哲学:在功能强大与工程实用性之间取得平衡。
什么是模式?
模式匹配与正则匹配⾮常相似,只是模式匹配中匹配的不仅有正则表达式,还可以有其他表达式。这⾥的“ 表达式” 就是我们将要介绍的模式。
常见的模式
1. 常量模式(Constant Pattern)
作用:判断一个值是否等于某个具体的常量。
示例:
fun constantPattern(a: Int) = when (a) {
1 -> "It is 1"
2 -> "It is 2"
else -> "It is other number"
}
- 解释:这就像 Java 中的 switch-case,根据传入的整数值进行分支判断。
- 特点:简单直观,适用于枚举或有限状态判断。
2. 类型模式(Type Pattern)
作用:判断一个对象的运行时类型,并自动进行类型转换。
示例:
fun getArea(shape: Shape) = when (shape) {
is Shape.Circle -> Math.PI * shape.radius * shape.radius
is Shape.Rectangle -> shape.width * shape.height
is Shape.Triangle -> shape.base * shape.height / 2.0
}
- 解释:when 结合 is 关键字,不仅能判断类型,还能在分支中智能类型转换(smart cast),无需手动强转。
- 特点:避免了冗长的 instanceof + 类型转换代码,安全且简洁。
3. 逻辑表达式模式(Logical Expression Pattern)
作用:不带参数的 when,用于判断任意布尔表达式。
示例:
fun logicPattern(a: Int) = when {
a in 2..11 -> "$a is smaller than 10 and bigger than 1"
else -> "Maybe $a is bigger than 10, or smaller than 1"
}
或者字符串判断:
fun logicPattern(a: String) = when {
a.contains("Yison") -> "Something is about Yison"
else -> "It's none of Yison's business"
}
- 解释:此时 when 更像是一系列 if-else if 的替代品,但语法更统一。
- 特点:可以组合任意条件判断,灵活性高。
处理嵌套表达式
if-else虽然能处理大多数模式,但是嵌套表达式⽤ if -else等语句实现起来比较困难
我们⾸先定义如下的结构:
sealed class Expr {
data class Num(val value: Int) : Expr()
data class Operate(val opName: String, val left: Expr, val right: Expr) : Expr()
}
- Num类表⽰某个整数的值;
- Operate类则是⼀个树形结构,它被⽤来表⽰⼀些复杂的表达式,其中opName 属性表⽰常见的操作符,⽐如
+、-、*、/。
进行整数表达式加法运算时,往往会遇到许多可以简化的表达式,比如1+0 => 1、 1*0 => 0,现在我们需要实现一个简单的需求: 将0+x或者x+0简化为x,其他情况返回表达式本身。
伪代码如下
x if expr == "0+x" or expr == "x+0" else expr
模仿Java风格,我们很容易写出这样的代码
fun simplifyExpr(expr: Expr): Expr = if(expr is Expr.Num) {
expr
}else if(expr is Expr.Operate&&expr.left is Expr.Num&&expr.left.value==0){
expr.right
}else if(expr is Expr.Operate&&expr.right is Expr.Num&&expr.right.value==0) {
expr.left
} else {
expr
}
在上面的代码中,我们进行了如下判断操作:
expr is Expr.Operate:判断给定的表达式是否为 Operate 类型;expr.opName == "+":判断该表达式是否为加法操作;expr.left is Expr.Num:判断表达式的左子节点是否为数值类型;expr.left.value == 0:判断左子节点的值是否等于 0。
这些条件共同用于识别特定结构的表达式,例如“0 + 某表达式”的情况,常用于表达式简化或模式匹配等场景。
因为在Kotlin中⽀持Smart Casts,⽽在Java 中是不⽀持的。所以如果我
们要⽤Java 实现,代码就会变成这样
Expr simplifyExpr(Expr expr) {
if (expr instanceof Expr.Num) {
return expr;
} else if (expr instanceof Expr.Operate) {
Expr.Operate operate = (Expr.Operate) expr;
if (operate.getLeft() instanceof Expr.Num && ((Expr.Num) operate.getLeft()).getValue() == 0) {
return operate.getRight();
} else if (operate.getRight() instanceof Expr.Num && ((Expr.Num) operate.getRight()).getValue() == 0) {
return operate.getLeft();
}
}
return expr;
}
假使定义的数据结构更加复杂,那么每⼀个条件都会包含更长串的代码, 既不⽅便阅 读,也容易出错。可以发现, if- else语句在处理
复杂的嵌套表达式的时候显得更加⽆⼒。
如果用when表达式,你可能会这么去做
fun simplifyExpr(expr: Expr): Expr = when {
expr is Expr.Num -> expr
expr is Expr.Operate && expr.opName=="+" && expr.left is Expr.Num && expr.left.value==0 -> expr.right
expr is Expr.Operate && expr.opName=="+" && expr.right is Expr.Num && expr.right.value==0 -> expr.left
else -> expr
}
这样其实和if-else语句实现没有什么区别,只是因为没有了嵌套才显得相对简洁,还是保留了很多判断语句。
改进思路,继续推进
我们在匹配嵌套表达式时,可以思考如何将要匹配的表达式进行解构操作,我们接着对simplifyExpr方法进行修改
fun simplifyExpr(expr: Expr): Expr = when(expr) {
Expr.Operate("+",Expr.Num(0),expr.right) -> expr.right
Expr.Operate("+", expr.left, Expr.Num(0)) -> expr.left
else -> expr
}
显然这样写编译器会报错,这是因为Kotlin的when表达式在匹配每个分支时不会先去判断分支的类型,我们需要手动写一个判断类型的操作
fun simplifyExpr(expr: Expr): Expr = when(expr) {
is Expr.Num -> expr
is Expr.Operate -> when(expr) {
Expr.Operate("+", Expr.Num(0),expr.right) -> expr.right
Expr.Operate("+", expr.left, Expr.Num(0)) -> expr.left
else -> expr
}
}
解释:
- 如果 expr 是一个数字(如 Num(5)),则直接返回它本身。
- 如果 expr 是一个操作表达式(如 a + b),则进入内部的 when 进一步判断是否可以化简。
- 匹配形如 0 + x 的表达式。返回x
- 匹配形如 x + 0 的表达式。返回x
- 如果不满足上述任何化简规则(比如是 2 + 3 或 a + b),则返回原表达式,
上面实现的 simplifyExpr 方法,与之前的版本相比,确实简洁了许多,也更直观地展现了待匹配表达式的结构。然而,当前的实现仍需手动编写类型判断语句,代码的声明性较弱。此外,当表达式嵌套层次较深时,仅依赖 when 表达式会显得力不从心。例如,若要匹配复杂的嵌套结构,代码将变得冗长且难以维护。如:
Expr.Operate("+", Expr.Num(0), Expr.Operate("+", Expr.Num(1), Expr.Num(2)))
我们可以通过使⽤递归的⽅式实现
fun simplifyExpr(expr: Expr): Expr = when(expr) {
is Expr.Num -> expr
is Expr.Operate -> when(expr) {
Expr.Operate("+", Expr.Num(0),expr.right) -> simplifyExpr(expr.right)
Expr.Operate("+", expr.left, Expr.Num(0)) -> expr.left
else -> expr
}
}
但是实际中的数据结构可能并不像上⾯那样对称,有可能在
某些情况下,我们必须访问两层结构,并且⽤递归又实现不了。那么我们就必须这样去写
fun simplifyExpr(expr: Expr): Expr = when (expr) {
is Expr.Num -> expr // 如果是数字,直接返回
is Expr.Operate -> when {
// 如果左边是 Num(0),右边是 Operate("+", something, Num(0))
(expr.left is Expr.Num && expr.left.value == 0) &&
(expr.right is Expr.Operate && expr.right.opName == "+" &&
expr.right.right is Expr.Num && expr.right.right.value == 0) -> expr.right.left
// 否则,返回原右边表达式(注意:不是原表达式)
else -> expr.right
}
}
1168

被折叠的 条评论
为什么被折叠?



