Java:枚举与if-then-else的性能比较

10

我在Google上没有找到关于此比较的简洁答案,而且我也不想浪费时间进行自己的评估,因此我想先问一下。

我相当确定使用枚举的 switch 语句会比 if-then-else 语句执行得更快,不过是否有明显的差异则是另一个问题。

有人能为我解答一下吗?


谢谢大家的快速回复,我将记住这点以备将来的项目使用。


3
我非常怀疑性能差异不会超过微小的优化。如果在你的情况下使用switch语句可以提供更易读和易于维护的代码,我建议你选择它。 - CoolBeans
我很好奇,是什么让你说“使用枚举的switch语句比if-then-else语句执行更快”? - Pops
@Lord Torgamus - 在 switch 语句中,每个 case 要么为真要么为假,在 if-then-else 语句中,每个 if 可以有多个布尔值,例如 this && that 或 this || that 等。我认为,评估多个值需要更多的时间。 - FizzBuzz
我很好奇你是否能得到任何可衡量的差异。对于这样的微基准测试,我建议使用http://code.google.com/p/caliper/,因为它非常易于使用,并由Guava团队设计。如果你测量了它,请告诉我们结果。 - maaartinus
2
在我看来,这并不是关于性能的问题 - 两者哪个更容易阅读,更易于维护,更少出现错误?这些应该是选择一种语言而非另一种语言的更重要动机,而不是性能。我很确定您已经猜到性能差异最多只能是微小的/边缘的。 - cthulhu
旁注:考虑用多态替换条件语句(Java枚举类型甚至支持抽象方法):http://www.refactoring.com/catalog/replaceConditionalWithPolymorphism.html - Puce
6个回答

8

没错,因为通常情况下,switch语句比if/else链的执行速度更快。

虽然生成的字节码并不总是性能比较的最终依据,但你可以检查它以获得更好的想法。

例如,这段代码:

class A { 
    enum N { ONE, TWO, THREE }
    void testSwitch( N e ) { 
        switch( e ) { 
            case ONE : x(); break;
            case TWO : x(); break;
            case THREE : x(); break;
        }
    }
    void testIf( Enum e ) { 
        if( e == N.ONE ) { x(); }
        else if( e == N.TWO ) { x(); }
        else if( e == N.THREE ) { x(); }
    }
    void x(){}
}

生成以下内容:

Compiled from "A.java"
class A extends java.lang.Object{
A();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   return

void testSwitch(A$N);
  Code:
   0:   getstatic   #2; //Field A$1.$SwitchMap$A$N:[I
   3:   aload_1
   4:   invokevirtual   #3; //Method A$N.ordinal:()I
   7:   iaload
   8:   tableswitch{ //1 to 3
        1: 36;
        2: 43;
        3: 50;
        default: 54 }
   36:  aload_0
   37:  invokevirtual   #4; //Method x:()V
   40:  goto    54
   43:  aload_0
   44:  invokevirtual   #4; //Method x:()V
   47:  goto    54
   50:  aload_0
   51:  invokevirtual   #4; //Method x:()V
   54:  return

void testIf(java.lang.Enum);
  Code:
   0:   aload_1
   1:   getstatic   #5; //Field A$N.ONE:LA$N;
   4:   if_acmpne   14
   7:   aload_0
   8:   invokevirtual   #4; //Method x:()V
   11:  goto    39
   14:  aload_1
   15:  getstatic   #6; //Field A$N.TWO:LA$N;
   18:  if_acmpne   28
   21:  aload_0
   22:  invokevirtual   #4; //Method x:()V
   25:  goto    39
   28:  aload_1
   29:  getstatic   #7; //Field A$N.THREE:LA$N;
   32:  if_acmpne   39
   35:  aload_0
   36:  invokevirtual   #4; //Method x:()V
   39:  return

void x();
  Code:
   0:   return

}

两种方法看起来都相当快。

因此,选择更易于维护的方法。


尽管 switch 通常更快,但当 case 语句稀疏和/或无序时,switch 并不是一个好的选择,这时它可能会失去优势...但是像任何微小的优化一样,时间测试才是唯一真正的答案 :-) - SteelBytes
三个枚举值不会有什么区别。试试用100个值,你会看到一个表查找与100个比较的差异。 - OrangeDog
@Orange 当然要在每个方法中调用不同的方法。 - OscarRyz

7

只需坚持编写最易读和理解的代码即可,我相信在寻找答案时已经失去了所有的性能优化所获得的时间。像这样的微小优化很少有价值,往往会导致代码比必要的更复杂。


1

我不确定哪个更快,但我猜两者都非常快。

我的考虑是,使用枚举和switch语句比使用多重if/else代码块更易读。

但要注意不要漏掉break语句!


1

是的,switch 语句通常比等效的 if / else 语句块执行得更快,因为编译器可以执行更多优化(通常,switch 块会被编译成分支表,而这在条件块中几乎不可能实现)。

我认为它们都更易读和更易维护(除非使用 fall-through cases,我建议不要使用!)

至于是否明显更快,这取决于您定义的“明显”程度。除非您追求某些非常特定的东西,否则您很可能根本不会注意到它,但我仍然会以可读性优势为主来做这件事(将速度优势视为奖励!)


(通常情况下,一个 switch 块会被编译成一个分支表,而这几乎是不可能用一块条件语句来实现的。)但是,如果编译器足够聪明,能够看到它只是一个变量列表与(数字)常量进行比较(这就是枚举在内部的本质),那么它不就能像处理 switch/case 一样进行优化吗? - cthulhu
@Cthulhu - 我应该说对于一般情况下的条件块是不可能做到的。是的,在你描述的情况下,理论上是可以的,但我不确定编译器是否实际进行了这种分析(在我看来,有比这个更值得集中注意的优化)。 - Michael Berry

1

对于这个问题,我的答案与通常的问题一样,即语言结构X是否比语言结构Y快:没有普遍的答案!

对于某些语言实现,例如Oracle(正式的Sun)基于Hotspot编译器的JVM,或IBM JDK在Z平台上,或OpenJDK在Linux上等,可能只有特定的答案。

因此,回答您的问题的唯一方法是进行适当的基准测试。要注意微基准测试,它们往往更容易出错而不是正确的,例如,请参见如何不编写微基准测试。如果您仍然想了解,请使用框架,其中描述在此

因此,我建议根据上下文中的适用性和可读性选择语言特性。


0
理论上,switch语句可以优化为单个计算的跳转,而if-else链必须保留为单独的比较。我不知道Java是否实际执行了这个优化。

无论如何,在可读性和可维护性方面,switch比if-else链更好,因此如果可能的话,请使用它们。


从内存中看,它确实执行了这个计算的跳转(它可以很容易地做到这一点,因为它限制了在 switch 中可以使用的类型为原始类型、枚举和(在 Java 7 及以上版本中)字符串)。 - Michael Berry
@berry120 - 我在考虑最优化优化(用nop填充案例,然后向前跳转enum.ordinal() * sizeOfCase)。 - OrangeDog

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接