PMD
告诉我
少于 3 个分支的 switch 效率低下,请改用 if 语句。
这是为什么?为什么是3?他们如何定义效率?
因为一条switch
语句是用两个特殊的 JVM 指令编译的,它们是lookupswitch
和tableswitch
。它们在处理大量案例时很有用,但当您只有几个分支时它们会导致开销。
相反,一条if/else
语句被编译成典型的je
jne
... 链,这些链更快,但在长链分支中使用时需要更多的比较。
您可以通过查看字节码看到差异,无论如何我不会担心这些问题,如果有任何问题可能会出现问题,那么 JIT 会处理它。
实际例子:
switch (i)
{
case 1: return "Foo";
case 2: return "Baz";
case 3: return "Bar";
default: return null;
}
被编译成:
L0
LINENUMBER 21 L0
ILOAD 1
TABLESWITCH
1: L1
2: L2
3: L3
default: L4
L1
LINENUMBER 23 L1
FRAME SAME
LDC "Foo"
ARETURN
L2
LINENUMBER 24 L2
FRAME SAME
LDC "Baz"
ARETURN
L3
LINENUMBER 25 L3
FRAME SAME
LDC "Bar"
ARETURN
L4
LINENUMBER 26 L4
FRAME SAME
ACONST_NULL
ARETURN
尽管
if (i == 1)
return "Foo";
else if (i == 2)
return "Baz";
else if (i == 3)
return "Bar";
else
return null;
被编译成
L0
LINENUMBER 21 L0
ILOAD 1
ICONST_1
IF_ICMPNE L1
L2
LINENUMBER 22 L2
LDC "Foo"
ARETURN
L1
LINENUMBER 23 L1
FRAME SAME
ILOAD 1
ICONST_2
IF_ICMPNE L3
L4
LINENUMBER 24 L4
LDC "Baz"
ARETURN
L3
LINENUMBER 25 L3
FRAME SAME
ILOAD 1
ICONST_3
IF_ICMPNE L5
L6
LINENUMBER 26 L6
LDC "Bar"
ARETURN
L5
LINENUMBER 28 L5
FRAME SAME
ACONST_NULL
ARETURN
尽管与使用 if 语句相比,使用 switch 的效率提升很小,但在大多数情况下,这些收益可以忽略不计。任何称职的源代码扫描器都会认识到微优化是代码清晰度的次要因素。
他们说如果 switch 非常短,if 语句比 switch 语句更易于阅读并且占用的代码行数更少。
来自PMD 网站:
TooFewBranchesForASwitchStatement:Switch 语句旨在用于支持复杂的分支行为。只在少数情况下使用 switch 是不明智的,因为 switch 不像 if-then 语句那样容易理解。在这些情况下,使用 theif-then 语句来增加代码的可读性。
这是为什么?
当 JIT 编译器(最终)将代码编译为本机代码时,会使用不同的指令序列。切换由执行间接分支的一系列本机指令实现。(该序列通常从表中加载一个地址,然后分支到该地址。) if / else 被实现为评估条件的指令(可能是比较指令),然后是条件分支指令。
为什么是3?
这是一个经验观察,我假设基于分析生成的本机代码指令和/或基准测试。(或者可能不是。为了绝对确定,您需要询问该 PMD 规则的作者他们是如何得出该数字的。)
他们如何定义效率?
执行指令所花费的时间。
我个人会对这条规则提出异议……或者更准确地说,是这条信息。我认为应该说一个if / else
语句比带有 2 个 case 的 switch 更简单、更易读。效率问题是次要的,可能无关紧要。
我相信这与 switch 和 if/else 的编译方式有关。
假设处理一个 switch 语句需要 5 次计算。假设一个 if 语句需要两次计算。您的 switch 中少于 3 个选项将等于 ifs 中的 4 个计算与 switch 中的 5 个计算。但是,交换机中的开销保持不变,因此如果它有 3 个选择,则 ifs 将被 3 * 2 处理,而交换机仍然为 5。
查看数百万次计算所获得的收益是极其微不足道的。它更多的是“这是更好的方法”,而不是任何可能影响您的事情。它只会在一个相当迭代中在该函数上循环数百万次的东西上这样做。