从printTriangularNumber到Duff's Device:掌握新旧Java Switch语句
在这篇博客文章中,我们将看到平凡的 Java switch 语句如何从一个“穿透”的奇特性演化成一个强大的表达式,以及理解其机制如何解锁像 Duff 设备这样的经典技巧。Java 的 switch 语句已经从容易导致穿透的结构演变为 Java 14 中引入的现代表达式语法。本文通过一个具体示例追溯了这一演变:一个通过故意让执行在没有 break 语句的情况下级联 case 来计算三角形数的方法。文章还将这种行为与 Duff 设备联系起来,这是一种 1983 年的循环展开技巧,它利用有意的穿透来处理剩余元素,然后再处理完整块。新旧 switch 语法的比较概述了各自的权衡,并提供了关于何时使用每种形式的实用指导。
偶然发现
我在准备 OCP Java 21 考试时,偶然遇到一个棘手的问题。一个名为 question2 的方法使用了一个没有 break 语句的 switch 语句。输出结果起初让我很惊讶。当我追溯执行过程后,我将该方法重命名为 printTriangularNumber。这一个重命名就说明了整个故事。这篇文章将深入探讨原因。
旧式 switch 语句
传统的 switch 语句自 Java 诞生之初就已经存在。其语法如下:
如上所示,每个 case 都以 break 结束。没有 break,执行不会停止,会继续进入下一个 case。旧式 switch 适用于 int、char、String 和 enum 类型。
穿透:特性还是 Bug?
switch 中最容易被误解的行为就是穿透。当你省略 break 时,执行会从当前 case 直接落入下一个 case。
输出:
大多数开发者将此视为潜在的 bug。他们并没有错。忘记写 break 是 Java 中最常见的错误之一。但有意的穿透则是另一回事。它是一种有意为之的工具。而 printTriangularNumber 正是完美的例子。
printTriangularNumber:穿透的实际应用
以下是我在准备 OCP 考试时从 question2 重命名的方法:
让我们追踪 n = 4:
- 跳转到
case 4,加 4。res = 4 - 穿透到
case 3,加 3。res = 7 - 穿透到
case 2,加 2。res = 9 - 穿透到
case 1,加 1。res = 10 - 命中
default,break
输出:10
每种输入的模式:
这就是 n * (n + 1) / 2,三角形数公式。穿透帮你完成了求和。每个 case 通过不停止来累加剩余的值。
对于 n = 0 或任何大于 5 的值,没有 case 匹配,default 立即触发,res 保持为 0。三元表达式打印 "Ok, bye."。
我个人认为这是一个有意使用语言语义的优美示例。这也是 OCP 考试喜欢考的类型。
新式 switch 表达式(Java 14+)
Java 14 将 switch 表达式作为标准特性引入。箭头语法 -> 完全消除了穿透。每个分支彼此独立。
需要注意几点:
- switch 现在是一个表达式,它返回一个值。
- 箭头
->同时替代了:和break。 - 没有穿透,每个分支独立执行。
- 单个分支支持多个标签:
case 1, 7 -> "Weekend";
你还可以内联使用它:
更简洁,更安全。
带 yield 的 switch 表达式
有时你在一个分支中需要多个语句。这时 yield 就派上用场了。
可以把 yield 理解为 switch 分支的 return 语句。当分支中有多个语句(位于 {} 内)时就需要使用它。一个常见错误是在 switch 表达式代码块中使用 return 而不是 yield。那样只有在方法内部编译时才会生效,并且它会从整个方法返回,而不仅仅是从 switch 返回。在 switch 表达式代码块中始终使用 yield。
Duff 设备:穿透的极致运用
现在我们已经充分理解了穿透,让我们看看最著名的有意使用穿透的例子:Duff 设备。Tom Duff 在 1983 年发明了这项技术,旨在通过减少循环分支开销来加速内存复制操作。其技巧是展开复制循环,并利用 switch 根据余数跳转到循环的中间位置。
在 Java 中,由于 Java 不允许 switch 和循环交错语法,我们分为两个清晰的阶段来实现:
让我们追踪 n = 13:
rem = 13 % 4 = 1- switch 跳转到
case 1,复制 1 个元素。i = 1 fullBlocks = (13 - 1) / 4 = 3- 循环执行 3 次,每次复制 4 个元素
- 总计:1 + 12 = 13 个元素
Python 的等价实现明确地分为两个阶段:
与 printTriangularNumber 的联系是直接的。两者都有意使用穿透。在 printTriangularNumber 中,switch 跳转到正确的 case 并向下累加。在 Duff 设备中,switch 跳转到正确的 case 并复制余数,然后主循环接管。
新旧 switch 快速对比
你应该使用哪一种?
对于新代码,始终优先使用带 -> 的 switch 表达式。它更安全、更简洁且更具表现力。你的代码审查者会感谢你。仅在真正需要级联行为时保留旧式 switch 并带穿透,例如 printTriangularNumber 或手工优化的循环(如 Duff 设备)。在这些情况下,添加注释说明意图。否则,下一个开发者(包括未来的你)会认为 break 是被意外遗漏的。
我个人的观察是:OCP Java 21 考试对两者都重点考察。判断穿透是有意还是无意,是考官探查的关键区别。确保你能够在没有运行的情况下追踪任意 switch 块的执行。
祝测试顺利!你怎么看:有意的穿透是巧妙的工程艺术,还是潜在的维护噩梦?在下方留下你的想法吧!