在Java中对枚举进行完全切换会产生"缺少返回语句"错误

Ale*_*kiy 7 java enums switch-statement

假设,我们有switch语句,它完全涵盖了enum参数的所有可能情况,并且也有null检查,不会被编译的原因"Missing return statement".

enum Foo {ONE,TWO}

int fooToInt(Foo foo) {
    if (foo == null) {
        throw new NullPointerException();
    }
    switch (foo) {
        case ONE: return 1;
        case TWO: return 2;
    }
}
Run Code Online (Sandbox Code Playgroud)

我知道,从defaultcase或enum之后抛出异常,或访问枚举元素而不是switch将解决问题.但我不明白这种行为的技术原因:显然,没有可能的执行分支,这不会导致returnthrow.在某些情况下,编译时检查是否涵盖所有案例都会很棒.

Tza*_*mon 6

由于您没有写默认值,编译器会在切换块后的下一行自动添加它.此时,编译器"注意到"该方法没有返回点并且给出了该错误.

我已经把你的例子改成了但是在切换后添加了RuntimeException,如下所示:

public class Example {

   enum Foo { ONE, TWO }

    int fooToInt(Foo foo) {
        if (foo == null) {
            throw new NullPointerException();
        }
        switch (foo) {
            case ONE: return 1;
            case TWO: return 2;
        }

        throw new RuntimeException("Should not have gotten here");
    }

    public static void main(String[] args) {

    }
}
Run Code Online (Sandbox Code Playgroud)

我编译了类并使用javap -c Example.class来查看实际的字节码(见下文).注意javac添加的"default:52".它导致切换后的块部分,在那里,我抛出了RuntimeException,它覆盖了返回的需要.

Compiled from "Example.java"
public class com.mprv.automation.jenkins.Example {
  public com.mprv.automation.jenkins.Example();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  int fooToInt(com.mprv.automation.jenkins.Example$Foo);
    Code:
       0: aload_1
       1: ifnonnull     12
       4: new           #2                  // class java/lang/NullPointerException
       7: dup
       8: invokespecial #3                  // Method java/lang/NullPointerException."<init>":()V
      11: athrow
      12: getstatic     #4                  // Field com/mprv/automation/jenkins/Example$1.$SwitchMap$com$mprv$automation$jenkins$Example$Foo:[I
      15: aload_1
      16: invokevirtual #5                  // Method com/mprv/automation/jenkins/Example$Foo.ordinal:()I
      19: iaload
      20: lookupswitch  { // 2
                     1: 48
                     2: 50
               default: 52
          }
      48: iconst_1
      49: ireturn
      50: iconst_2
      51: ireturn
      52: new           #6                  // class java/lang/RuntimeException
      55: dup
      56: ldc           #7                  // String Should not have gotten here
      58: invokespecial #8                  // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
      61: athrow

  public static void main(java.lang.String[]);
    Code:
       0: return
}
Run Code Online (Sandbox Code Playgroud)


Kon*_*kov 5

编译器不会检查是否已列出所有常量Foo作为case块,因此会引发错误.

假设它Foo被定义为:

enum Foo {ONE,TWO,THREE}
Run Code Online (Sandbox Code Playgroud)

那么,如果你Foo.THREE作为参数传递,你的方法会返回什么?


作为该switch方法的替代方法,您可以intFoo枚举中添加成员,并为每个常量设置相应的数字:

enum Foo {
    ONE(1),TWO(2);

    int value;
    Foo(int value) {
        this.value = value;
    }
}
Run Code Online (Sandbox Code Playgroud)

这样你就不需要了switch,编译器会请你为任何可能的新Foo常量设置一个相应的数字.

  • 如果你没有列出所有`enum`常量,一些编译器支持生成警告,那么说编译器没有检查那个是不正确的 - 但编译器不允许*让你逃脱而不指定如何处理未知值.毕竟,在编译了`switch`语句之后,可以在`enum`类型*中添加一个新的常量. (5认同)

Jac*_*hle 5

我要参加这里的黑暗中拍摄,没有看过这方面的任何理由,但如果这不是主要的行为的原因,它至少是一个原因.

假设enum来自您的项目所依赖的库.在您编译的版本中,ONE并且TWO是唯一的选项.但是,你可能最终运行针对更高版本(通过OSGi的或其他解决方案)已经又增加了价值THREE.如果THREE传递给fooToInt它,它将到达方法的末尾,并且不会返回(或抛出)任何内容.哎呀.

这在运行时发现是相当不愉快的,所以你不得不选择如何处理它,即使在编译时它实际上似乎是不可能的.有些情况,例如你的例子中的情况,可能会被检测到并允许编译,而其他情况可能会以不同的方式处理(例如隐式throw),但是在可以用来改进Java的所有事情的列表中,我不会不要把它放在最顶层.