问题

为什么很多程序员不用 switch,而是大量的 if...else if ...?

回答
这是一个非常普遍的现象,并且有很多原因导致了程序员更倾向于使用 `if...else if...` 而不是 `switch`。下面我将详细地阐述这些原因,并从多个角度进行分析。

核心原因总结:

尽管 `switch` 在某些特定场景下非常高效,但 `if...else if...` 在灵活性、可读性、处理复杂条件、与现有代码的兼容性以及避免特定陷阱方面,通常表现更优,因此成为了更普遍的选择。

详细阐述:

1. 灵活性与表达能力:

`switch` 的局限性:
仅支持等值比较: `switch` 语句的核心是判断一个表达式的值是否等于 case 后面的常量。它无法直接处理范围判断(例如 `x > 10`)、区间判断(例如 `10 <= x <= 20`)、或者更复杂的布尔逻辑(例如 `(a > 5 && b < 10) || c == 'X'`)。
数据类型限制: 在大多数语言中,`switch` 语句的表达式和 `case` 的常量通常要求是整型、字符型、枚举型,或者某些支持等值比较的特定类型(如字符串在某些现代语言中也支持)。它不能直接用于浮点数、指针、复杂的对象或者布尔值(虽然布尔值可以看作是0/1的整型,但直接用true/false作为case也有限制)。
无法直接处理布尔表达式: 如果你想根据多个布尔变量的组合情况进行判断,`switch` 就显得力不从心。你需要将这些布尔变量组合成一个可以被 `switch` 处理的整数或枚举。

`if...else if...` 的优势:
支持任意布尔表达式: `if...else if...` 可以包含任何有效的布尔表达式,包括相等、不相等、大于、小于、大于等于、小于等于、逻辑与(&&)、逻辑或(||)、逻辑非(!),甚至函数调用返回布尔值等。这使得它可以处理非常复杂和细致的条件判断。
处理范围和区间: 使用 `if...else if...` 可以轻松处理 `if (score >= 90)`、`else if (score >= 80)`、`else if (score >= 70)` 等区间判断,这在 `switch` 中是无法直接实现的。
处理多种数据类型: `if...else if...` 可以用于几乎所有数据类型,包括数字、字符串、布尔值、对象、浮点数等。

示例:

假设我们要根据用户分数给出一个等级:

使用 `switch` (不直接可行或非常笨拙):
`switch (score)` 无法直接处理分数范围。你可能会想到将分数分段,例如 `switch (score / 10)`,但这只能处理整十的范围,对于细微的边界处理会很麻烦。

使用 `if...else if...` (直观且易于实现):
```java
if (score >= 90) {
grade = 'A';
} else if (score >= 80) {
grade = 'B';
} else if (score >= 70) {
grade = 'C';
} else {
grade = 'D';
}
```
这个例子清晰地展示了 `if...else if...` 在处理范围判断上的天然优势。

2. 可读性与维护性:

`switch` 的潜在可读性问题:
`break` 语句的疏忽(Fallthrough 陷阱): C/C++ 等语言中,`switch` 语句如果没有 `break` 语句,会“贯穿”到下一个 `case` 中,这是一种常见的错误来源。虽然现代语言(如 Java, C, Swift, Python)在很多情况下默认是“break”的,但在遗留代码或跨语言使用时,这种“fallthrough”行为可能导致难以发现的 bug。即便有默认 break,忘记写 break 仍然可能是一个潜在问题。
代码结构化差(当 case 很多时): 当 `switch` 语句的 `case` 非常多时,整个代码块会变得非常长,可读性会下降。而且,`switch` 的结构相对固定,很难在 `case` 中插入复杂的逻辑或辅助变量。
枚举类型限制: 虽然枚举类型与 `switch` 结合很强大,但如果项目不使用枚举,或者枚举的映射关系不是简单的等值关系,`switch` 就变得不适用了。

`if...else if...` 的优势:
清晰的逻辑分支: 每个 `if` 或 `else if` 子句都清晰地表达了一个特定的条件,整个结构更像是在描述一个决策流程,更容易理解。
灵活的逻辑组合: 可以在每个分支内部编写独立的逻辑,包括声明局部变量、调用函数、进行更复杂的计算等,而不会影响其他分支。
易于插入 `break` 或 `return`: 在每个条件分支的末尾,可以自由地使用 `break` (跳出循环或 `switch`) 或 `return` (退出函数),这种控制流程的明确性有助于代码的结构化和避免意外的贯穿。

3. 性能考量(何时 `switch` 占优):

这是一个很多人会考虑的点。在某些情况下,`switch` 的性能确实可能优于 `if...else if...`:

基于跳转表(Jump Table): 对于一个常量整型表达式,编译器可以将 `switch` 语句翻译成一个跳转表(jump table)。这是一个数组,数组的索引对应 `case` 的值,数组的元素则是指向相应 `case` 代码块的指针。查找的时间复杂度是 O(1) 的常数级别,比线性查找(`if...else if...` 在最坏情况下需要依次检查所有条件)要快得多。
何时 `switch` 性能优势明显: 当 `case` 的值是连续的整数范围,或者离散且数量较多,并且没有重叠的条件时,编译器优化成的跳转表效果最好。

何时 `if...else if...` 可能更优或持平:
条件之间存在重叠或逻辑复杂: 如果条件之间有重叠(例如 `if (x > 10)` 和 `if (x > 5)`),或者条件本身很复杂(如 `if (a && b || c)`),`switch` 就无法使用跳转表优化,编译器可能会将其退化为一系列的比较,此时 `if...else if...` 的性能可能更接近甚至更好。
`case` 数量少: 当 `case` 的数量很少时,`if...else if...` 的线性查找开销也很小,与 `switch` 的跳转表查找相比,差异可以忽略不计。
非整型或非常量条件: 如果 `switch` 无法进行跳转表优化,它可能就会退化成一系列的比较,性能上就与 `if...else if...` 没有本质区别。
编译器的优化能力: 现代编译器非常智能,它们会尽力优化代码。对于某些简单的 `if...else if...` 链条,编译器也可能将其优化成类似跳转表的结构,尤其是在条件非常规律的情况下。

总结性能部分: 虽然 `switch` 在特定场景下有性能优势,但这种优势不是绝对的。由于 `if...else if...` 的通用性,以及现代编译器强大的优化能力,除非有明确的性能瓶颈且可以通过 `switch` 优化解决,否则过度追求 `switch` 的性能可能得不偿失,并且牺牲了灵活性和可读性。

4. 兼容性与历史包袱:

遗留代码库: 在很多大型、历史悠久的代码库中,代码风格和设计模式已经固化。如果大量的代码已经使用了 `if...else if...`,那么新增代码也倾向于遵循这种风格,以保持一致性。修改现有代码以适配 `switch` 可能需要大量的重构工作,并可能引入新的风险。
团队习惯: 在一个团队中,如果大家普遍习惯使用 `if...else if...` 来处理条件判断,那么遵循团队习惯是更高效的工作方式。

5. 避免特定语言陷阱:

C/C++ 的 Fallthrough: 如前所述,忘记 `break` 是 C/C++ 中 `switch` 语句一个非常普遍且难以察觉的错误来源。很多程序员因为害怕引入这种 bug,或者之前吃过亏,所以更倾向于使用更安全的 `if...else if...`。
枚举的隐式转换: 在一些语言中,枚举值可以隐式转换为整数。虽然这在 `switch` 中很有用,但在 `if` 条件中可能需要显式转换,或者需要注意类型匹配问题。

什么时候 `switch` 是更好的选择?

尽管有上述种种原因,`switch` 在以下场景下仍然是优秀甚至更优的选择:

处理枚举类型: 当你要根据一个枚举变量的不同取值执行不同操作时,`switch` 是最自然、最清晰、最高效的方式。
处理固定且离散的整数值: 当你要根据一个整数变量的几个特定值(如 `0`, `1`, `2` 等)执行操作时,`switch` 的可读性和潜在性能会更好。
状态机实现: 在实现状态机时,`switch` 经常被用来根据当前状态执行不同的行为。

结论:

总而言之,程序员大量使用 `if...else if...` 而不是 `switch` 的根本原因是 灵活性、表达能力、可读性、易维护性以及避免潜在陷阱。`if...else if...` 提供了一个更通用的工具箱来处理各种复杂的条件逻辑,而 `switch` 更多的是一种针对特定模式(等值比较、离散常量)的优化。

虽然性能是考虑因素之一,但现代编译器已经极大地缩小了两者在许多常见场景下的性能差距。除非遇到明确的性能瓶颈,并且确认 `switch` 能够有效优化,否则在处理大多数条件判断时,`if...else if...` 因其通用性和易用性而成为程序员的默认选择。它更像是一个万能钥匙,而 `switch` 是一个针对特定锁孔的高效工具。

网友意见

user avatar

说来也是巧最近在看 Dubbo 源码,然后发现了一处很奇怪的代码,刚好和这个 switch 和 if else 有关!

让我们来看一下这段代码,它属于 ChannelEventRunnable,这个 runnable 是 Dubbo IO 线程创建,将此任务扔到业务线程池中处理。

看到没,把 state == ChannelState.RECEIVED 拎出来独立一个 if,而其他的 state 还是放在 switch 里面判断。

我当时脑子里就来回扫描,想想这个到底有什么花头,奈何知识浅薄一脸懵逼。

于是就开始了一波探险之旅!

原来是 CPU 分支预测

遇到问题当然是问搜索引擎了,一般而言我会同时搜索各大引擎,咱这也不说谁比谁好,反正有些时候度娘还是不错的,比如这次搜索度娘给的结果比较靠前,google 较靠后。

一般搜索东西我都喜欢先在官网上搜,找不到了再放开搜,所以先这么搜 site:xxx.com key

你看这就有了,完美啊!

我们先来看看官网的这篇博客怎么说的,然后再详细地分析一波。

Dubbo 官网的博客

现代 CPU 都支持分支预测 (branch prediction) 和指令流水线 (instruction pipeline),这两个结合可以极大提高 CPU 效率。对于像简单的 if 跳转,CPU 是可以比较好地做分支预测的。但是对于 switch 跳转,CPU 则没有太多的办法。 switch 本质上是根​据索引,从地址数组里取地址再跳转。

也就是说 if 是跳转指令,如果是简单的跳转指令的话 CPU 可以利用分支预测来预执行指令,而 switch 是要先根据值去一个类似数组结构找到对应的地址,然后再进行跳转,这样的话 CPU 预测就帮不上忙了。

然后又因为一个 channel 建立了之后,超过99.9%情况它的 state 都是 ChannelState.RECEIVED,因此就把这个状态给挑出来,这样就能利用 CPU 分支预测机制来提高代码的执行效率。

并且还给出了 Benchmark 的代码,就是通过随机生成 100W 个 state,并且 99.99% 是 ChannelState.RECEIVED,然后按照以下两种方式来比一比(这 benchSwitch 官网的例子名字打错了,我一开始没发现后来校对文章才发现)。

虽然博客也给出了它的对比结果,但是我还是本地来跑一下看看结果如何,其实 JMH 不推荐在 ide 里面跑,但是我懒,直接 idea 里面跑了。

从结果来看确实通过 if 独立出来代码的执行效率更高(注意这里测的是吞吐),博客还提出了这种技巧可以放在性能要求严格的地方,也就是一般情况下没必要这样特殊做。

至此我们已经知道了这个结论是对的,不过我们还需要深入分析一波,首先得看看 if 和 switch 的执行方式到底差别在哪里,然后再看看 CPU 分支预测和指令流水线的到底是干啥的,为什么会有这两个东西?

if vs switch

我们先简单来个小 demo 看看 if 和 switch 的执行效率,其实就是添加一个全部是 if else 控制的代码, switch 和 if + switch 的不动,看看它们之间对比效率如何(此时还是 RECEIVED 超过99.9%)。

来看一下执行的结果如何:

好家伙,我跑了好几次,这全 if 的比 if + switch 强不少啊,所以是不是源码应该全改成 if else 的方式,你看这吞吐量又高,还不会像现在一下 if 一下又 switch 有点不伦不类的样子。

我又把 state 生成的值改成随机的,再来跑一下看看结果如何:

我跑了多次还是 if 的吞吐量都是最高的,怎么整这个全 if 的都是最棒滴。

反编译 if 和 switch

在我的印象里这个 switch 应该是优于 if 的,不考虑 CPU 分支预测的话,当从字节码角度来说是这样的,我们来看看各自生成的字节码。

先看一下 switch 的反编译,就截取了关键部分。

也就是说 switch 生成了一个 tableswitch,上面的 getstatic 拿到值之后可以根据索引直接查这个 table,然后跳转到对应的行执行即可,也就是时间复杂度是 O(1)。

比如值是 1 那么直接跳到执行 64 行,如果是 4 就直接跳到 100 行。

关于 switch 还有一些小细节,当 swtich 内的值不连续且差距很大的时候,生成的是 lookupswitch,按网上的说法是二分法进行查询(我没去验证过),时间复杂度是 O(logn),不是根据索引直接能找到了,我看生成的 lookup 的样子应该就是二分了,因为按值大小排序了。

还有当 switch 里面的值不连续但是差距比较小的时候,还是会生成 tableswtich 不过填充了一些值,比如这个例子我 switch 里面的值就 1、3、5、7、9,它自动填充了2、4、6、8 都指到 default 所跳的行。

让我们再来看看 if 的反编译结果

可以看到 if 是每次都会取出变量和条件进行比较,而 switch 则是取一次变量之后查表直接跳到正确的行,从这方面来看 switch 的效率应该是优于 if 的。当然如果 if 在第一次判断就过了的话也就直接 goto 了,不会再执行下面的哪些判断了。

所以从生成的字节码角度来看 switch 效率应该是大于 if 的,但是从测试结果的角度来看 if 的效率又是高于 switch 的,不论是随机生成 state,还是 99.99% 都是同一个 state 的情况下。

首先 CPU 分支预测的优化是肯定的,那关于随机情况下 if 还是优于 switch 的话这我就有点不太确定为什么了,可能是 JIT 做了什么优化操作,或者是随机情况下分支预测成功带来的效益大于预测失败的情形?

难道是我枚举值太少了体现不出 switch 的效果?不过在随机情况下 switch 也不应该弱于 if 啊,我又加了 7 个枚举值,一共 12 个值又测试了一遍,结果如下:

好像距离被拉近了,我看有戏,于是我背了波 26 个字母,实不相瞒还是唱着打的字母。

扩充了分支的数量后又进行了一波测试,这次 swtich 争气了,终于比 if 强了。

题外话: 我看网上也有对比 if 和 switch 的,它们对比出来的结果是 switch 优于 if,首先 jmh 就没写对,定义一个常量来测试 if 和 switch,并且测试方法的 result 写了没有消费,这代码也不知道会被 JIT 优化成啥样了,写了几十行,可能直接优化成 return 某个值了。

小结一下测试结果

对比了这么多我们来小结一下。

首先对于热点分支将其从 switch 提取出来用 if 独立判断,充分利用 CPU 分支预测带来的便利确实优于纯 swtich,从我们的代码测试结果来看,大致吞吐量高了两倍。

在热点分支的情形下改成纯 if 判断而不是 if + swtich的情形下,吞吐量提高的更多。是纯 switch 的 3.3 倍,是 if + switch 的 1.6 倍。

在随机分支的情形下,三者差别不是很大,但是还是纯 if 的情况最优秀。

但是从字节码角度来看其实 switch 的机制效率应该更高的,不论是 O(1) 还是 O(logn),但是从测试结果的角度来说不是的。

在选择条件少的情况下 if 是优于 switch 的,这个我不太清楚为什么,可能是在值较少的情况下查表的消耗相比带来的收益更大一些?有知道的小伙伴可以在文末留言。

在选择条件很多的情况下 switch 是优于 if 的,再多的选择值我就没测了,大伙有兴趣可以自己测测,不过趋势就是这样的。

CPU 分支预测

接下来咱们再来看看这个分支预测到底是怎么弄的,为什么会有分支预测这玩意,不过在谈到分支预测之前需要先介绍下指令流水线(Instruction pipelining),也就是现代微处理器的 pipeline。

CPU 本质就是取指执行,而取指执行我们来看下五大步骤,分别是获取指令(IF)、指令解码(ID)、执行指令(EX)、内存访问(MEM)、写回结果(WB),再来看下维基百科上的一个图。

当然步骤实际可能更多,反正就是这个意思需要经历这么多步,所以说一次执行可以分成很多步骤,那么这么多步骤就可以并行,来提升处理的效率。

所以说指令流水线就是试图用一些指令使处理器的每一部分保持忙碌,方法是将传入的指令分成一系列连续的步骤,由不同的处理器单元执行,不同的指令部分并行处理。

就像我们工厂的流水线一样,我这个奥特曼的脚拼上去了马上拼下一个奥特曼的脚,我可不会等上一个奥特曼的都组装完了再组装下一个奥特曼。

当然也没有这么死板,不一定就是顺序执行,有些指令在等待而后面的指令其实不依赖前面的结果,所以可以提前执行,这种叫乱序执行

我们再说回我们的分支预测。

这代码就像我们的人生一样总会面临着选择,只有做了选择之后才知道后面的路怎么走呀,但是事实上发现这代码经常走的是同一个选择,于是就想出了一个分支预测器,让它来预测走势,提前执行一路的指令。

那预测错了怎么办?这和咱们人生不一样,它可以把之前执行的结果全抛了然后再来一遍,但是也有影响,也就是流水线越深,错的越多浪费的也就越多,错误的预测延迟是10至20个时钟周期之间,所以还是有副作用的。

简单的说就是通过分支预测器来预测将来要跳转执行的那些指令,然后预执行,这样到真正需要它的时候可以直接拿到结果了,提升了效率。

分支预测又分了很多种预测方式,有静态预测、动态预测、随机预测等等,从维基百科上看有16种。

我简单说下我提到的三种,静态预测就是愣头青,就和蒙英语选择题一样,我管你什么题我都选A,也就是说它会预测一个走势,一往无前,简单粗暴。

动态预测则会根据历史记录来决定预测的方向,比如前面几次选择都是 true ,那我就走 true 要执行的这些指令,如果变了最近几次都是 false ,那我就变成 false 要执行的这些指令,其实也是利用了局部性原理。

随机预测看名字就知道了,这是蒙英语选择题的另一种方式,瞎猜,随机选一个方向直接执行。

还有很多就不一一列举了,各位有兴趣自行去研究,顺便提一下在 2018 年谷歌的零项目和其他研究人员公布了一个名为 Spectre 的灾难性安全漏洞,其可利用 CPU 的分支预测执行泄漏敏感信息,这里就不展开了,文末会附上链接。

之后又有个名为 BranchScope 的攻击,也是利用预测执行,所以说每当一个新的玩意出来总是会带来利弊。

至此我们已经知晓了什么叫指令流水线和分支预测了,也理解了 Dubbo 为什么要这么优化了,但是文章还没有结束,我还想提一提这个 stackoverflow 非常有名的问题,看看这数量。

为什么处理有序数组要比非有序数组快?

这个问题在那篇博客开头就被提出来了,很明显这也是和分支预测有关系,既然看到了索性就再分析一波,大伙可以在脑海里先回答一下这个问题,毕竟咱们都知道答案了,看看思路清晰不。

就是下面这段代码,数组排序了之后循环的更快。

然后各路大神就蹦出来了,我们来看一下首赞的大佬怎么说的。

一开口就是,直击要害。

You are a victim of branch prediction fail.

紧接着就上图了,一看就是老司机。

他说让我们回到 19世纪,一个无法远距离交流且无线电还未普及的时候,如果是你这个铁路交叉口的扳道工,当火车快来的时候,你如何得知该扳哪一边?

火车停车再重启的消耗是很大的,每次到分叉口都停车,然后你问他,哥们去哪啊,然后扳了道,再重启就很耗时,怎么办?猜!

猜对了火车就不用停,继续开。猜错了就停车然后倒车然后换道再开。

所以就看猜的准不准了!搏一搏单车变摩托。

然后大佬又指出了关键代码对应的汇编代码,也就是跳转指令了,这对应的就是火车的岔口,该选条路了。

后面我就不分析了,大伙儿应该都知道了,排完序的数组执行到值大于 128 的之后肯定全部大于128了,所以每次分支预测的结果都是对了!所以执行的效率很高。

而没排序的数组是乱序的,所以很多时候都会预测错误,而预测错误就得指令流水线排空啊,然后再来一遍,这速度当然就慢了。

所以大佬说这个题主你是分支预测错误的受害者。

最终大佬给出的修改方案是咱不用 if 了,惹不起咱还躲不起嘛?直接利用位运算来实现这个功能,具体我就不分析了,给大家看下大佬的建议修改方案。

最后

这篇文章就差不多了,今天就是从 Dubbo 的一段代码开始了探险之旅,分析了波 if 和 switch,从测试结果来看 Dubbo 的这次优化还不够彻底,应该全部改成 if else 结构。

而 swtich 从字节码上看是优于 if 的,但是从测试结果来看在分支很多的情况下能显示出优势,一般情况下还是打不过 if 。

然后也知晓了什么叫指令流水线,这其实就是结合实际了,流水线才够快呀,然后分支预测预执行也是一个提高效率的方法,当然得猜的对,不然分支预测错误的副作用还是无法忽略的,所以对分支预测器的要求也是很高的。

JMH 的测试代码我也打个包,想自己跑的同学后台输入「分支预测」即可获取

参考

Spectre :freebuf.com/vuls/160161

Dubbo 博客 :dubbo.apache.org/zh-cn/

stackoverflow.com/quest

en.wikipedia.org/wiki/I

en.wikipedia.org/wiki/B


没想到有这么多赞哈,贴个我个人GitHub

上面有我的很多文章汇总,稍微截一些~

大家觉得有帮助的话,给我来个赞哟~ 关注 @是Yes呀

user avatar

现实世界是复杂而混乱的,而应用开发要解决的就是现实世界的问题,只有让代码迁就现实世界,而不是反过来。

switch 的条件只能是同一类型的不同值,比如颜色

case red

case blue

。。。

而现实世界有些时候遇到的问题是这样的

if color == red

else if shape == rectangle

else if height > 6 and color != blue

else

。。。

写数学算法 计算机基础框架的时候要幸福得多,毕竟此时世界是被清晰定义,有严谨逻辑的,但是一旦写业务逻辑代码,就要面对复杂混乱的现实世界,而现实世界问题才是占比最高的,这样的代码也是占总代码的绝大多数。

user avatar

如果按面向对象重构原则来说,switch本身也是很不够妥当的的,除了Command类似模式下,凡是可以用switch的地方,都应该考虑使用继承类或其它设计模式来实现,以减少过于复杂的逻辑分支,增加代码可读性。

并且在一些基础语言中,switch很多时候不好使用,比如它不支持字符串,比如它不支持复杂逻辑判断并且default比起if else明显会出现很多的思维负担,用起来思维并不流畅。

更关键的是临时的功能需求往往是多变的,在实际使用时,才会发现问题所在,于是要增减功能,无论之后期是否要重构代码,用if else时,风险都会大大降低,更加有灵活性。

user avatar

分支非常多的 if 和 else if 往往并不是一次写出来的,而是每次增加新功能时就开个新的分支。对于每一个增加分支的人来说,他们都不觉得自己有责任要重构整段代码,因为他们只想用最低的成本把自己要做的事情做完,于是代码质量变得越来越低。

一般来说,如果 if 和 else if 分支超过 3 个就可以考虑写成 switch。如果 switch 的分支超过 10 个就可以考虑写成 config,然后专门写一个函数根据 config 来做 mapping。如果需要进行的映射逻辑很复杂,但使用频率很高,可以考虑做一个专门的 rule engine 来处理这件事情,或者是一门 DSL。

user avatar

这个问题如果是一般的程序员还真不知道为啥,只知道别人都是这样写的,所以跟着这样写了,很少有人会深入地去思考这个问题,你能有这个疑问,说明你有一颗求真的心(我当年也想过 )。

这个问题对编程没有一定功底,是答不出个所以然的。我认为主要可以从编码习惯程序性能上去思考。

编码习惯

我首先来说一下编码习惯,先不说其它的,单从代码行数来看的话,实现相同的逻辑,switchif...else if占用的行数是要多一些的,请看下面的示例代码:

switch版

       public class Demo1 {     public static void main(String[] args) {         long s = System.nanoTime(), e;         int a = 0, b;         for (int i = 0; i < 10000000; i++) {             b = i % 10;             switch (b) {                 case 1:                     a = a + 2;                     break;                 case 2:                     a = a + 3;                     break;                 case 3:                     a = a + 4;                     break;                 case 4:                     a = a + 5;                     break;                 case 5:                     a = a + 6;                     break;                 case 6:                     a = a + 7;                     break;                 case 7:                     a = a + 8;                     break;                 case 8:                     a = a + 9;                     break;                 case 9:                     a = a + 10;                     break;             }         }         e = System.nanoTime();         System.out.printf("运行时间:%.2f毫秒", (e - s) / 1000000f);     } }     

if ... eles if 版

       public class Demo2 {     public static void main(String[] args) {         long s = System.nanoTime(), e;         int a = 0, b;         for (int i = 0; i < 10000000; i++) {             b = i % 10;             if (b == 1) {                 a = a + 2;             } else if (b == 2) {                 a = a + 3;             } else if (b == 3) {                 a = a + 4;             } else if (b == 4) {                 a = a + 5;             } else if (b == 5) {                 a = a + 6;             } else if (b == 6) {                 a = a + 7;             } else if (b == 7) {                 a = a + 8;             } else if (b == 8) {                 a = a + 9;             } else if (b == 9) {                 a = a + 10;             }         }         e = System.nanoTime();         System.out.printf("运行时间:%.2f毫秒", (e - s) / 1000000f);     } }     

上面两个示例类Demo1Demo2实现的逻辑一模一样,Demo1使用switch实现,代码总行数是40行;而Demo2使用if ... else if 实现,总共只有30行(这里显示不了行号,你可以把代码复制到自己的编辑器去看)。

单从这点来看,if ... else if就完胜了 ,毕竟程序员每多敲一个字母,对他手的伤害就会大一些,程序员的手还有其它很重要的用途的 !所以...新手程序员们要保护好自己的手哈。

说到新手,这里就不得不提使用switch的一个坑了,一般新手都会犯这样的错误,就是忘记写break、忘记写break、忘记写break,这个问题我见过90%的新手都会遇到,break不写编译不会报错,因为在语法上来说,不写也是对的,只是写于不写,所表达的意思不一样。

既然语法是对的,编译当然能通过。因为编译主要就是检测程序的语法是否合格,合格就将源码转换成机器可以执行的代码。所以这也是为什么不写break,还能编译通过的原因。

但程序在执行时,有break和没有break,结果会完全不一样,就拿上面Demo1的例子来说,少了一个break,变量a的结果都会有变化。所以,对于新手来说,switch中的break就是一个隐藏的炸弹,没用好就会产生bug。

但如果你的编程水平高,能彻底明白有break和没有break的区别,那在某些场景还是挺有用的,再来看下面的示例代码:

switch 版:

       import java.util.Scanner;  public class Demo3 {     public static void main(String[] args) {         System.out.print("请输入你的姓名:");         Scanner scanner = new Scanner(System.in);         String name = scanner.next();         switch (name) {             case "张三":             case "李四":                 System.out.println("欢迎你");                 break;             case "王五":                 System.out.println("对不起,这里不欢迎你");                 break;             default:                 System.out.println("你叫啥?");                 break;         }     } }     

if ... else if 版

       import java.util.Scanner;  public class Demo4 {     public static void main(String[] args) {         System.out.print("请输入你的姓名:");         Scanner scanner = new Scanner(System.in);         String name = scanner.next();         if ("张三".equals(name) || "李四".equals(name)) {             System.out.println("欢迎你");         } else if ("王五".equals(name)) {             System.out.println("对不起,这里不欢迎你");         } else {             System.out.println("你叫啥?");         }     } }     

这个示例的逻辑很简单,在控制台输入姓名,然后如果是张三或李四,就打印欢迎你,对王五打印不欢迎的内容。

这里如果仅从代码行数去看的话,还是使用if ... else if占优势,但从代码的可阅读性和可扩展性来说的话,明显使用switch更合适。

为什么这么说?举个栗子,业务上现在是要求对张三和李四说欢迎,如果隔壁的产品经理哪天想给你找点事干,要求对赵一、赵二、赵三等等都说欢迎,那在Demo4里面得不断往if的条件后再追加条件;而在switch版本里面只需要追加一个case,而且看上去还一目了然,结构非常清晰,示例如下:

       import java.util.Scanner;  public class Demo3 {     public static void main(String[] args) {         System.out.print("请输入你的姓名:");         Scanner scanner = new Scanner(System.in);         String name = scanner.next();         switch (name) {             case "赵一": // 追加的需求             case "张三":             case "李四":                 System.out.println("欢迎你");                 break;             case "王五":                 System.out.println("对不起,这里不欢迎你");                 break;             default:                 System.out.println("你叫啥?");                 break;         }     } }     

另外,在某些场景使用switch也会增加程序的复杂性和可读性。就拿上面的Demo1的代码来说,如果想要在switchcase 1里面跳出当前的for循环,那该怎样做呢?

       public class Demo1 {     public static void main(String[] args) {         long s = System.nanoTime(), e;         int a = 0, b;         for (int i = 0; i < 10000000; i++) {             b = i % 10;             switch (b) {                 case 1:                     a = a + 2;                     // 在此次如何跳出for循环???                     break;                 case 2:                 // 此处省略诺干行......             }         }     } }     

大家知道for循环中要跳出当前的for循环,也是使用break关键字,但此处是在switch里面,switch里的break是退出当前的case。所以。。。 对于小白来说,这里就懵了!

真的,一般人还真不知道怎样处理这个问题,有人会想到立一个flag,在switch结束后去判断这个flag,或者直接判读i的值,比如下面这样:

       public class Demo1 {     public static void main(String[] args) {         long s = System.nanoTime(), e;         int a = 0, b;         for (int i = 0; i < 10000000; i++) {             b = i % 10;             switch (b) {                 case 1:                     a = a + 2;                     break;                 case 2:                 // 此处省略诺干行......             }             // 在此处判断,满足条件就跳出循环             if (i == 0) {                 break;             }         }     } }     

还有人可能会用到Java中的标签语法,就像这样:

       public class Demo1 {     public static void main(String[] args) {         long s = System.nanoTime(), e;         int a = 0, b;         LABEL_FOR:// 给此处的for循环定义一个标签         for (int i = 0; i < 10000000; i++) {             b = i % 10;             switch (b) {                 case 1:                     a = a + 2;                     break LABEL_FOR; // 跳出for循环                 case 2:                 // 此处省略诺干行......             }         }     } }     

这种方法一般人也想不到,因为很少有人这样写,有人写了四五年的代码都不一定见过,但这的确是Java中支持的语法。

虽然这样比上面的判断方法看上去优雅,但增加了程序的复杂性和可读性。如果使用的是if else if,根本不用搞这么复杂,直接使用break即可,就像这样:

       public class Demo2 {     public static void main(String[] args) {         long s = System.nanoTime(), e;         int a = 0, b;         for (int i = 0; i < Integer.MAX_VALUE; i++) {             b = i % 10;             if (b == 1) {                 a = a + 2;                 break; // 此处的break可以直接跳出当前for循环             } else if (b == 2) {                 // 此处省略诺干行......     

所以,从编码风格来说的话,需要结合实际的业务需求去选择使用哪种语法。

程序性能

从性能上来说的话,如果不是做大量的运算的话,性能上是几乎没有差异的。

就拿上面的Demo1Demo2来说的话,两个程序运行耗时都是差不多。

但如果把for循环的次数加大几倍,其结果就明显了,看下面的示例和结果:

switch 版

       public class Demo1 {     public static void main(String[] args) {         long s = System.nanoTime(), e;         int a = 0, b;         for (int i = 0; i < Integer.MAX_VALUE; i++) {             b = i % 10;             switch (b) {                 case 1:                     a = a + 2;                     break;                 case 2:                     a = a + 3;                     break;                 case 3:                     a = a + 4;                     break;                 case 4:                     a = a + 5;                     break;                 case 5:                     a = a + 6;                     break;                 case 6:                     a = a + 7;                     break;                 case 7:                     a = a + 8;                     break;                 case 8:                     a = a + 9;                     break;                 case 9:                     a = a + 10;                     break;             }         }         e = System.nanoTime();         System.out.printf("运行时间:%.2f毫秒", (e - s) / 1000000f);     } }     

if ... else if 版

       public class Demo2 {     public static void main(String[] args) {         long s = System.nanoTime(), e;         int a = 0, b;         for (int i = 0; i < Integer.MAX_VALUE; i++) {             b = i % 10;             if (b == 1) {                 a = a + 2;             } else if (b == 2) {                 a = a + 3;             } else if (b == 3) {                 a = a + 4;             } else if (b == 4) {                 a = a + 5;             } else if (b == 5) {                 a = a + 6;             } else if (b == 6) {                 a = a + 7;             } else if (b == 7) {                 a = a + 8;             } else if (b == 8) {                 a = a + 9;             } else if (b == 9) {                 a = a + 10;             }         }         e = System.nanoTime();         System.out.printf("运行时间:%.2f毫秒", (e - s) / 1000000f);     } }     

以上代码中,将for循环的次数改为了Integer.MAX_VALUE。再来看一下两个程序运行的结果:

先贴上我的机器配置:

说明:不同的机器配置,运行耗时会不一样。

Demo1运行3次的结果

       运行时间:4464.96毫秒 运行时间:4501.70毫秒 运行时间:4660.67毫秒     

Demo2运行3次的结果

       运行时间:5892.39毫秒 运行时间:6014.14毫秒 运行时间:5440.15毫秒     

通过对比会发现,在做大量运算时,switch的性能更优。

所以,如果你看过一些框架的源码就会发现那些作者他们很多地方都用的是switch,就是这个原因。

小结

  • 从编码习惯上来说,switchif ... else if各有利弊,需要结合实际的业务需求去选择使用哪种语法。
  • 如果程序性能上说的话,一般的程序中使用switchif ... else if几乎没有差异,只有在需要做大量的运算,switch的优势才能体现。

----------------------------------------------------

觉得有用的麻烦点赞关注走一波,谢谢!!!

user avatar

我曾经接手过一份代码,遇到过一个三十几个if else套if else的模块。

心理骂骂咧咧谁他喵写的这玩意,然后开始review历史。

大致情况是这样的:第一个程序员写下这段代码时,只有两个if else;后来开始逐渐加需求,先是一个、两个,随后量变引起质变,于是逻辑分支快速扩张。

这个时候已经没有人愿意去重构成switch或是其他什么设计模式了,毕竟复杂度摆在那里,万一崩了还得背锅。

三四个程序员接手这段代码之后,就变成我现在这种局面了。

第一个程序员绝对没有料到这么简单的逻辑在之后会变成这么复杂的模块,甚至在增添第一第二条else时,也只是很随意的加上。

所以我觉得,这个锅绝对是是甲方的,让他娘的随便改需求。

这么一想心里就好受多了,编程嘛,最重要的是要看的开。

于是我又增加了两条else,测试,提交,下班。











————————————————

回来更新一波,下文与本题目无关,算私货:

papi酱更新了一个视频,别人是看着看着就笑了,我是看着看着就哭了。

bilibili.com/video/av37

之前的回答算是皮一下,带有一点自我调侃的意味。但大环境的事实如何,各位想必也是心知肚明的,不然也不会有这么多赞同了。

有时候真的不是我们不想写好代码,是不能写好代码。写着写着需求砍了、需求变了,什么设计模式都不顶用,最终还是怎样快怎样方便怎样来,因为根本没人知道这段代码还能不能活的过下一段需求变动。

有的人肯定要说怎么不订合同。合同肯定是有的,但是明明白纸黑字写的合同,该改还是得改,毕竟你要是不同意甲方那些“微小的变动”,以后还做不做了?!金主真能去得罪?

还是要学会得过且过,跟什么过不去也不能跟自己过不去,糟糕的代码忍一忍就完了:代码能跑、头发不少,对我们这些打工的人而言比什么都重要。

现实工作绝不是课本中的理想状态,会有无数的突发情况在等着你。你定义了半天观察者、备忘录,第二天这部分需求被砍了;写了半天接口,抽象类,忽然下午告诉你要加个十万八千里打不着边的啥东西,于是又开始加适配器,等你加完了告诉你又砍了。甚至有次半夜被喊起来改代码,等改完了发现需求被撤回了,气的我直接请了两天假调整心情。

设计模式和大的框架绝对是一个项目中非常重要的东西,但不是绝对重要的;一个好的PM团队,在某种意义上,才真正决定了这个项目的代码质量。

user avatar

1、语法上并没有更简单。

2、break心智负担。

3、适用范围太小,为了统一美观还不如都用if else if

类似的话题

  • 回答
    这是一个非常普遍的现象,并且有很多原因导致了程序员更倾向于使用 `if...else if...` 而不是 `switch`。下面我将详细地阐述这些原因,并从多个角度进行分析。 核心原因总结:尽管 `switch` 在某些特定场景下非常高效,但 `if...else if...` 在灵活性、可读性、.............
  • 回答
    这事儿说起来,也挺有意思的,很多时候咱们写代码,尤其是刚入行那会儿,习惯性地就敲出了一长串 `if...else if...else`,感觉这样清晰明了,能把各种情况都顾全了。但你仔细扒拉扒拉,会发现很多老司机、或者说在一些特定场景下, `switch` 语句其实是个更优雅、更高效的选择。那么,为什.............
  • 回答
    .......
  • 回答
    这问题触及到了不少程序员内心的真实想法,也揭示了独立开发者和普通打工人的巨大差异。说实话,想靠一个小众应用“月入数万”,这并非天方夜谭,但确实不是人人都能做到的。而大多数程序员宁愿“上班”,背后有很多层原因,绝非简单一句“懒”或者“没想法”就能概括的。为什么“小众应用月入数万”听起来诱人?首先,得明.............
  • 回答
    的确,在很多人的想象中,程序员应该是一群拥有强大逻辑思维,能够创造出酷炫应用、改变世界的“数字巫师”。他们敲击键盘,代码便如魔法般飞舞,构建出数字世界的种种奇迹。从某种意义上说,这本身就是一件足够酷的事情。然而,在国内,“程序员”这个词汇,却常常伴随着“无聊”、“呆板”、“格子衬衫”、“加班到深夜”.............
  • 回答
    这个问题挺沉重,也挺真实的。说实话,看到那些废寝忘食、头发一把把掉、眼睛熬得通红的程序员,心里确实会有点不是滋味。有时候觉得他们好像被代码绑架了,生活就只剩下屏幕和键盘。为什么会让人感觉“不像生活”?这其实有很多方面的原因,我们一个个来看: 工作性质的“吞噬”: 编程这行,很多时候不是朝九晚五能.............
  • 回答
    “程序员一到 Deadline 干活效率超高” 这个说法,虽然在很多情况下是真实的,但背后的原因却非常复杂,而“把 Deadline 定得很短”这个看似简单的解决方案,实际上会带来一系列连锁反应,并且往往适得其反。让我们来详细剖析一下其中的原因: 为什么程序员到 Deadline 效率会提高?—— .............
  • 回答
    很多人不喜欢程本《红楼梦》的后四十回,这其中原因复杂且深刻,并非单一原因能够完全概括。我们可以从多个维度来详细解析:一、 风格与思想上的断裂: 文笔风格的转变: 这是最直观也最常被诟病的一点。曹雪芹的原笔文风细腻、含蓄、诗意盎然,充满了淡淡的哀愁和对人情世故的深刻洞察。而后四十回虽然也在模仿,但.............
  • 回答
    “胡斐渣男”这个标签,说实话,我一开始听到也觉得挺诧异的。毕竟在《雪山飞狐》和《飞狐外传》里,胡斐给我的印象更多的是一个侠肝义胆、重情重义的好汉。那么,为什么会有这么多人,尤其是在网络上,给他扣上“渣男”的帽子呢?归根结底,问题就出在他对程灵素的态度上,以及这种态度在读者心中激起的强烈不公平感。咱们.............
  • 回答
    要说《三体》的读者普遍“讨厌”程心,同时对叶文洁的“讨厌”程度相对较低,这其中确实有些值得玩味的原因。这不仅仅是简单的角色好恶,更涉及到读者对道德、责任、人性以及故事走向的理解和情感投射。为什么程心招致广泛的“讨厌”?程心之所以让很多读者感到不适甚至“讨厌”,主要源于以下几个层面: “圣母”式的.............
  • 回答
    程序员的薪资水平,在很多人的印象里,确实是相当不错的,甚至可以说站在了许多行业的前沿。然而,即便坐拥令人艳羡的收入,程序员群体中依然存在着普遍的担忧和不满,这背后隐藏着一系列复杂且深层次的原因。这并非是贪得无厌,而是多方面因素共同作用下的结果。首先,行业的快速迭代与技能焦虑是绕不开的一个坎。技术的世.............
  • 回答
    为什么很多程序员对String的执行效率耿耿于怀? 深度解析程序员对 String 的执行效率之所以“耿耿于怀”,并非空穴来风,而是源于 String 在很多编程语言中,特别是 Java、C 等面向对象语言中,其 不可变性(Immutability) 以及由此带来的一系列设计和实现上的考量。这种“耿.............
  • 回答
    要说程序员为啥对 Vim 情有独钟,这事儿说起来可就话多咯。它不像那些花里胡哨的IDE(集成开发环境)一样,上来就把所有东西都摆在你面前,让你眼花缭乱。Vim 就跟一位老工匠一样,朴实无华,但内功深厚,一旦你摸透了它的脾气,那效率提升的可不是一点半点。首先,最直观的,Vim 的核心是它的模式化操作。.............
  • 回答
    哈哈,这个问题挺有意思的!我认识的很多程序员朋友,尤其是那些每天对着电脑屏幕敲敲打打的,家里都有那么一两只毛茸茸的猫主子。这可不是巧合,背后绝对有些说得通的理由,而且越琢磨越觉得有道理。首先,咱们得从程序员这个职业本身说起。我们这行,尤其是搞技术的,经常要面对复杂的问题,需要高度的专注和耐心。写代码.............
  • 回答
    许多程序员,尤其是那些深入接触开发和系统管理的人,确实会觉得 Linux 在很多方面比 Windows 更方便、更有效率。这并非绝对,Windows 本身也在不断进步,并且在某些领域有其优势。但从程序员的核心需求来看,Linux 的设计哲学和生态系统往往能更好地满足他们的工作流程。要理解这一点,我们.............
  • 回答
    这确实是个很有意思也很值得探讨的问题。你观察到的现象——国外程序员博客做得好,甚至能赚钱,而国内相对少见,而且影响力不如国外——这背后牵扯到很多层面的原因,绝非一两句话能概括的。咱们就掰开了揉碎了聊聊,看看这中间到底是怎么回事。国外程序员博客的“繁荣景象”是怎么来的?首先,咱们得搞清楚国外为啥这么多.............
  • 回答
    网上流传的“程序员抑郁、猝死”的说法,绝非空穴来风,背后有着真实的生活写照和行业痛点。网友们之所以对程序员群体抱有同情和心疼,也是因为他们看到了这个群体所承受的巨大压力和不为人知的艰辛。首先,我们来聊聊为什么会有“程序员容易抑郁、猝死”的说法,以及这个群体为何会让网友们感到心疼。1. 高强度、长时间.............
  • 回答
    说起为什么会有这么多中国人选择去日本当程序员,这背后其实是一系列复杂的因素交织作用的结果,并非单一原因可以概括。要详细讲清楚,咱们得把这背后的“为什么”掰开了揉碎了聊。首先,得从“外面”和“里面”两个角度来看。“外面”:日本作为程序员的热门目的地,它有什么吸引力?1. 技术需求旺盛,尤其是对高级人.............
  • 回答
    .......
  • 回答
    这个问题啊,其实挺有意思的,也挺普遍的。你问为什么有些程序员显得“傲慢”,这背后可不是一层原因那么简单,而是很多因素交织在一起的结果,而且这种“傲慢”的表现形式也多种多样,有时候是出于自信,有时候则是一种自我保护。首先,我们得承认,程序员这个群体,尤其是那些技术能力特别强的人,确实容易展现出一种旁人.............

本站所有内容均为互联网搜索引擎提供的公开搜索信息,本站不存储任何数据与内容,任何内容与数据均与本站无关,如有需要请联系相关搜索引擎包括但不限于百度google,bing,sogou

© 2025 tinynews.org All Rights Reserved. 百科问答小站 版权所有