【问题标题】:Complete switch on enum in Java produces "Missing return statement" error在 Java 中完全打开枚举会产生“缺少返回语句”错误
【发布时间】:2016-08-30 10:52:53
【问题描述】:

假设,我们有switch语句,它完全覆盖了枚举参数的所有可能情况,并且也有null-check,不会被编译为"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;
    }
}

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

【问题讨论】:

  • 您可能希望在每个案例之后或至少在最后一个案例之后break
  • 真的没必要写if (foo == null) { throw new NullPointerException(); },考虑到当switch(foo)被执行并且foonull时会发生什么……
  • Holger,写它只是为了表明所有可能的分支都被明确地覆盖了。

标签: java enums switch-statement


【解决方案1】:

我将在这里暗中尝试,没有阅读任何理由,但如果这不是该行为的主要原因,那么至少是一个原因。

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

这在运行时发现是相当不愉快的,因此你不得不选择如何处理它,即使在编译时它实际上看起来是不可能的。某些情况,例如您的示例中的情况,可能会被检测到并允许编译,而其他情况可以以不同方式处理(例如隐含的throw),但在所有事情的列表中这可以用来改进 Java,我不会把它放在首位。

【讨论】:

    【解决方案2】:

    由于你没有写默认,编译器会在 switch 块之后的下一行自动添加它。此时,编译器“注意到”该方法没有返回点,并给出错误。

    我以你为例进行了更改,但在切换后添加了 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) {
    
        }
    }
    

    我已经编译了这个类并使用 javap -c Example.class 来查看实际的字节码(见下文)。请注意 javac 添加的“默认值:52”。 它通向 switch 案例之后的块部分,在那里,我抛出了 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
    }
    

    【讨论】:

      【解决方案3】:

      编译器不会检查您是否已将 Foo 中的所有常量列为 case 块,因此会引发错误。

      假设Foo被定义为:

      enum Foo {ONE,TWO,THREE}
      

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


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

      enum Foo {
          ONE(1),TWO(2);
      
          int value;
          Foo(int value) {
              this.value = value;
          }
      }
      

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

      【讨论】:

      • 由于某些编译器支持在您不列出所有enum 常量时生成警告,因此说编译器不检查是不正确的——但编译器不是 允许 让您在不指定如何处理未知值的情况下逃脱。毕竟,在enum 类型 switch 语句已经编译之后,可以添加一个新常量。
      【解决方案4】:

      原因是编译器实际上并没有通过你的开关来检查你是否已经实现了每个案例,它只是检查现有案例是否对应于 Foo 中的实际值 - 因此它需要一些东西,默认块或返回。

      【讨论】:

        【解决方案5】:

        您错过了default 块。

        因为你的方法必须返回一些值。

        return 放在 switch 语句之外。或者把 default 屏蔽

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2013-09-18
          • 1970-01-01
          • 1970-01-01
          • 2014-01-09
          • 2014-08-20
          • 2013-04-27
          • 2012-11-19
          • 1970-01-01
          相关资源
          最近更新 更多