当前位置: 首页 > article >正文

Kotlin 智能类型转换与 when 表达式(八)

导读大纲

      • 1.0.1 智能转换: 结合类型检查和类型转换
      • 1.0.2 重构: 用 when 表达式替换 if 表达式
      • 1.0.3 块作为 if 和 when 的分支

  1. when 表达式专题系列
    • 从枚举类引出 when 表达式
    • 多种形式的 when 表达式

1.0.1 智能转换: 结合类型检查和类型转换

  1. 将编写一个函数来计算简单的算术表达式,比如 (1 + 2) + 4

    • 这些表达式只包含一种类型的运算: 两个数字之和
      1. 其他算术运算(如减法、乘法和除法)也可以用类似的方法实现
    • 在此过程中,您将了解智能转换
      1. 如何让处理不同类型的 Kotlin 对象变得更加容易
  2. 首先,如何对表达式进行编码

    • 传统的做法将表达式存储在树状结构
      1. 其中每个节点要么是一个和(Sum),要么是一个数(Num)
      2. Num 总是叶子节点,而Sum节点有两个子节点: 求和操作的参数
    • <1> 用于编码表达式简单类结构
      1. 一个名为 Expr 的接口, 没有声明任何方法
        • 被用作一个标记接口,为不同类型的表达式提供通用类型
      2. 实现该接口的两个类 Num 和 Sum
    • <2> 要标记一个类实现一个接口
      1. 可以在类的主构造器使用冒号(:)+接口名
      2. Num 类实现 Expr 接口带有一个Int类型的属性
      3. Sum 类也实现 Expr 接口并带有 left 和 right 两个Expr类型的属性
        • 这里意味着它们可以是 Num 或 Sum
          Expr、Num和Sum之间关系的类图
interface Expr                                         // <1>
class Num(val value: Int) : Expr                       // <2>
class Sum(val left: Expr, val right: Expr) : Expr      // <2>
  1. 比如要存储前面提到的表达式 (1 + 2) + 4

    • 需要创建一个 Expr 对象结构,具体为 Sum(Sum(Num(1), Num(2)),Num(4))
    • 下图显示该结构的树形表示
      1. 目标是计算这种由 Sum 和 Num 对象组成的表达式
        title
  2. Expr 接口有两个实现,因此你必须尝试两个选项评估表达式的结果值

    • 如果表达式是数字(Num),则返回相应的值
    • 如果是求和(Sum),则必须递归评估左右表达式,并返回它们的和
  3. 首先,我们将查看该函数的实现,其编写风格类似于 Java 代码

    • 然后,我们将对其进行重构,以反映 Kotlin的本土风格
    • 最初,您可能会编写一个类似于其他语言风格的函数
      1. 使用一系列 if 表达式来检查 Expr 的不同子类型
    • <1> 在 Kotlin 中,您可以使用 is 检查检查变量是否属于某种类型
      1. 如果您使用 C# 编程,可能会对 is 语法感到熟悉
        • Java 开发人员可能会将其等同于 instanceof
    • <2> 智能转换将类型检查和类型转换合并为一个操作
      1. 这里使用"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
}
  1. Kotlin 的 "is"检查提供一些额外的便利
    • 如果你检查变量的特定类型,那么之后你就不需要再对它进行显式转换
      1. 你可以把它当作已检查过的类型来使用
    • 实际上,编译器会为你执行类型转换,我们称之为智能类型转换
    • 在 eval 函数中,在检查变量 e 是否具有 Num 类型
      1. 编译器会将其智能地解释为 Num 类型的变量
      2. 然后,您就可以访问 Num 类型的属性,而无需进行显式转换
    • 在IntelliJ IDEA和Android Studio中,这些智能转换的值会用背景色凸显
      1. 因此很容易掌握该值是否事先经过检查
      2. 同时也可以看到下图中"as Num"背景是灰色, 表示多余
        title

1.0.2 重构: 用 when 表达式替换 if 表达式

  1. 在前面的学习中,我们已经知道 if 在 Kotlin 中是一个表达式
    • 这也是 Kotlin 中没有三元操作符的原因–if 表达式可以返回一个值
    • <1> 这意味着你可以重写 eval 函数,使用表达式体语法
      1. 去掉 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")
    }
  1. 你还可以让这段代码更加简洁
    • 如果 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")
  1. 使用 when 重写它,这里我们使用不同形式的 when 分支
    • 允许我们检查 when 参数值的类型
    • <1> when分支对参数类型进行检查和智能转换
      1. 第一个分支检查通过,则认为e是Num类型,即可以直接访问Num类型的属性
      2. 同上,可以直接访问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 的分支

  1. if 和 when 都可以将代码块作为分支

    • 在这种情况下,块中的最后一个表达式就是结果
  2. 假设你想深入了解 eval 函数是如何计算结果的

    • 一种方法是添加 println 语句,记录函数当前正在计算的内容
    • <1> 你可以为 when 表达式中的每个分支在代码块中添加println语句
      1. 代码块中的最后一个表达式作为结果返回
      2. 在 is Num 分支, e.value 表达式作为结果返回
      3. 在 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")
    }
  1. 代码块中的最后一个表达式即为结果的规则
    • 适用于所有可以使用代码块期望得到结果的情况
      1. 比如上面示例中的 when 分支
    • 在之后学到的异常捕获中,这条规则同样适用于try body和catch子句
      1. 也将讨论这条规则在 lambda 表达式中的应用
    • 但正如之前提到的, 这条规则并不适用于常规的函数
      1. 函数的代码块体必须显式使用 return 语句
      2. 代码块形式的函数与表达式形式的函数

http://www.kler.cn/news/316878.html

相关文章:

  • 828华为云征文 | 云服务器Flexus X实例,Docker集成搭建Redis集群
  • 实战19-详情页UI4等分
  • 浅谈C#之SynchronizationContext
  • Fyne ( go跨平台GUI )中文文档- 架构 (八)完结
  • Openpyxl 插入数据添加数据
  • leetcode 437.路径总和III
  • Gitlab runner的简单使用(一)
  • 【Redis】之Geo
  • 红黑树:强大的数据结构之插入详解,附图
  • yolov8多任务模型-目标检测+车道线检测+可行驶区域检测-yolo多检测头代码+教程
  • Spring IOC容器Bean对象管理-注解方式
  • 每日一题——第九十四题
  • pywinauto:Windows桌面应用自动化测试(十)
  • 成都睿明智科技有限公司怎么样?
  • 【深度】为GPT-5而生的「草莓」模型!从快思考—慢思考到Self-play RL的强化学习框架
  • Shader 中的光源
  • 民主测评系统可以解决哪些问题?
  • Typescript进阶:解锁Infer类型的使用
  • C++笔记---set和map
  • NLP--自然语言处理学习-day1
  • 《微信小程序实战(3) · 推广海报制作》
  • 文件系统(软硬链接 动静态库 动态库加载的过程)
  • C++学习笔记(32)
  • 在C#中使用NPOI将表格中的数据导入excel中
  • 工业交换机如何保证数据的访问安全
  • SkyWalking 简介
  • 深入理解Go语言中的并发封闭与for-select循环模式
  • 使用脚本自动化管理外部Git仓库依赖
  • 如何基于Flink CDC与OceanBase构建实时数仓,实现简化链路,高效排查
  • MySQL面试题——第一篇