【问题标题】:Java forcing default return statement from method with enum switch with all enum values coveredJava强制使用枚举开关的方法返回默认语句,覆盖所有枚举值
【发布时间】:2019-01-09 12:17:47
【问题描述】:

我有以下示例情况:

public void shouldReturnStringForEnum() {
    MessageType myType = getType();
    System.out.println(getMessageForType(myType));
}

String getMessageForType(MessageType myType) {
    switch(myType) {
        case error:
            return "Error type";
        case warning:
            return "Warning type";
        case info:
            return "Info type";
    }
} // <= error: missing return statement

MessageType getType() {
    Random random = new Random();
    return MessageType.values()[random.nextInt(3)];
}

enum MessageType {error, warning, info }

除了从switch 语句的主体之外,我无法弄清楚从方法getMessageForType 返回的可能性是什么。 我在想:

  • 从 I/O 数据反序列化 - 但随后 java.lang.IllegalArgumentException: No enum constant 发生在调用 getMessageForType 之前很久
  • 可能的 null 方法参数 - 但随后它将在 switch(myType) 评估中失败,java.lang.NullPointerException

在这种情况下强制使用默认的 return 语句是很不舒服的,因为我不知道在这里返回什么。在这种情况下抛出异常在这里也没有任何意义。这种行为背后的设计决策是什么?

请帮忙,我在这里缺少什么?

【问题讨论】:

  • 其他一些想法:A)当您切换到从中派生字符串时,为什么要使用枚举?为什么不将该字符串 build 到您的枚举中,这样您就可以转到myType.getWhateverText()?! B) 一个用 @Test 注释的测试,它不做 assert 或其他类型的真正验证......但只是一个 print ...不是一个测试;-)
  • 嗨,这里的返回类型无关紧要,@Test 只是处理这种特殊情况的一种形式。我会删除它,以免混淆

标签: java enums switch-statement return


【解决方案1】:

如果您稍后编辑枚举,添加一个新常量,而不重新编译开关,会发生什么?这种情况要提防。

default: throw new AssertionError();很正常。

【讨论】:

  • 对我来说很直观,向枚举类型添加新值会导致编译错误。事实上,在这种情况下我会抛出异常,但我一直困扰着为什么解释器不“知道”我涵盖了所有场景,也许有一个场景我不知道。
  • 如果有可能出现未知枚举的情况,那么我认为代码应该适当地处理它。至少应该抛出一个 RuntimeException。大多数代码通常不能很好地处理错误(捕获 Throwable 的人吗?)所以在这里使用断言(或者在这种情况下抛出它的错误)似乎是一种逃避。
  • 为什么你希望它被大多数代码处理,而不是立即出错?
  • 因为我希望代码能够容错。或者我正在写一个别人使用的库。我想要做好防御性编程而不是立即解决我的应用程序错误的原因有很多。
【解决方案2】:

在这种情况下抛出异常在这里也没有任何意义。

这是有道理的,因为即使您知道/认为您永远不会进入这里,您也必须处理这种情况以使您的代码符合 Java 编译规则,该规则期望该方法在任何情况下都返回 String

您可以避免异常抛出,但它会使您的代码不那么健壮:

String getMessageForType(MessageType myType) {
   switch(myType) {
     case error:
        return "Error type";
     case warning:
        return "Warning type";       
   }
   return "Info type";
 }

假设添加了一个fine 枚举值并且您没有更新getMessageForType(),您将返回“信息类型”而不是。

所以这是一个很好的方法:

String getMessageForType(MessageType myType) {
   switch(myType) {
      case error:
        return "Error type";
    case warning:
        return "Warning type";
    case info:
        return "Info type";
   }
   throw new RuntimeException("Should not happen here ! We get the enum value " + myType);
 }

更好的方法是将与每个枚举关联的String 作为枚举的实例字段添加:

enum MessageType {

 ERROR("Error type"), WARNING("Warning type"), INFO("Info type");

  private String msg;

  MessageType(String msg){ 
    this.msg = msg;
  }

  public String getMsg(){
     return msg;
  }
}

通过这种方式,您不再需要 switch 以及 getMessageForType() 方法。
shouldReturnStringForEnum() 方法可以很简单:

@Test
public void shouldReturnStringForEnum() {
    System.out.println(getType().getMsg());
}

【讨论】:

    【解决方案3】:

    Java 强制从具有枚举开关的方法中返回默认语句,并覆盖所有枚举值...这种行为背后的设计决策是什么?

    这是语言做出的一个非常重要(且正确)的决定。即使您当前的代码处理了枚举的所有当前值,但这并不意味着枚举类可能会在您的代码编译后很长时间内发生变化。您可能会升级 3rd 方库,它可能会添加另一个枚举值,导致您的代码在运行时无效,而没有默认值。

    即使您控制了枚举代码,但这并不意味着其他开发人员(或未来的您)可能会向枚举添加另一个值并且无法更新 switch 语句。以这种方式编写向前兼容的代码通常是一种最佳实践,在这种情况下,对于语言强制执行该行为至关重要。

    我不知道在这里返回什么。

    然后问题归结为是否抛出异常。如何处理无效的枚举值或其他异常情况是我们程序员每天都在努力解决的问题。在这种情况下,您需要问自己想要发生什么?这是一个不应该抛出的烦恼还是一个更严重的错误?调用者应该处理异常还是RuntimeException ok?这些是您需要在应用程序的上下文中回答的问题。

    最后,我不了解你,但我认识的大多数程序员都会剪切和粘贴大量代码。尽管此枚举可能永远不会扩展,但未来的枚举扩展,并且做出正确的决定可能会受益于适当处理这种情况。

    在这种情况下强制使用默认的 return 语句是不方便的......

    "Unknown type" 可能是个不错的选择。

    case ...:
         return ...;
    default:
         // here in case someone updates the enum and forgets to update this code
         return "Unknown type";
    

    在这种情况下抛出异常在这里也没有任何意义。

    这在一定程度上取决于返回默认 "unknown" 字符串的交易量有多大。如果有一个缺少 case 条目的新枚举,您希望它抛出异常吗?

    对于例外情况,您可能希望使用:

    case ...:
         return ...;
    default:
         throw new IllegalStateException("unknown enum type found for" + mType);
    

    或者IllegalArgumentException

    【讨论】:

    • 好吧,我希望在我扩展未覆盖我的 switch 语句中的枚举值之后发生错误。根据此处的答案,我得出的结论是,在不扩展枚举的情况下,没有其他方法可以从此方法返回。这只是解释器的一个例子,它根本不“知道”枚举值。
    • 抱歉,我无法真正解析您的 cmets。如果你在枚举中添加了元素(我认为这里不应该使用“扩展”这个词),那么你的代码肯定会编译得很好。该类当然“知道”枚举值,只是您的 code 没有考虑未来的值。这与口译员无关。否则我不明白你的意思。
    【解决方案4】:

    您可以使用defaultdefault 情况在switch 语句中使用默认值return,如下所示:

    String getMessageForType(MessageType myType) {
        switch(myType) {
            case error:
                return "Error type";
            case warning:
                return "Warning type";
            case info:
                return "Info type";
            default:
                return "unknown message type";
        }
    } // <= no error anymore
    

    您也可以在 switch 语句之前的 String 中定义一个默认值,并在 switch 中不匹配的情况下将其作为默认值返回:

    String getMessageForType(MessageType myType) {
        String r = "unknown message type";
        switch(myType) {
            case error:
                r = "Error type";
            case warning:
                r = "Warning type";
            case info:
                r = "Info type";
        }
        return r;
    } // <= no error anymore
    

    【讨论】:

    • 但是这种方法的客户端会如何处理“未知的消息类型”呢?如果我想要一个未知的消息类型,我会在我的枚举值中表示它。我认为抛出异常是更好的选择,因为在编译时没有意识到可能的枚举值
    • 当然,要么客户端有办法处理未知消息类型,要么你只需抛出AssertionErrorRuntimeException 或类似的...
    猜你喜欢
    • 2011-10-27
    • 1970-01-01
    • 2020-03-03
    • 1970-01-01
    • 1970-01-01
    • 2013-09-23
    • 2021-08-24
    • 2016-09-23
    • 1970-01-01
    相关资源
    最近更新 更多