Java强制从带有枚举开关的方法中返回默认返回语句,并覆盖所有枚举值

4

I have the following example situation:

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数据进行反序列化 - 但是在调用getMessageForType之前很久就会发生java.lang.IllegalArgumentException: No enum constant
  • 可能为空的方法参数 - 但是它将在评估switch(myType)时失败,出现java.lang.NullPointerException

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

请帮忙看看,我错过了什么?


一些其他的想法:A)如果您要从枚举中派生字符串,为什么要使用枚举?为什么不将该字符串构建到您的枚举中,这样您就可以使用myType.getWhateverText()!B)用@Test注释的测试如果没有进行assert或其他类型的真实验证...而只是print...那就不是一个测试;-) - GhostCat
1
嗨,这里返回类型并不重要,@Test只是一种形式,用于处理特定情况。为了避免混淆,我将其删除。 - agienka
4个回答

3
如果您稍后编辑枚举并添加新常量,而不重新编译开关语句会发生什么情况? 这就是正在防范的情况。
default: throw new AssertionError(); 完全是正常的。

对我来说,将新值添加到枚举类型应该会导致编译错误,这是很直观的。实际上,在这种情况下,我会抛出异常,但有一段时间我一直在想为什么解释器不“知道”我已经涵盖了所有情况,或许还存在我不知道的情况。 - agienka
如果可能出现未知枚举的情况,我认为代码应该适当地处理。至少应该抛出一个RuntimeException。大多数代码通常无法很好地处理错误(捕获Throwable吗?),因此在这里使用断言(或在这种情况下抛出其错误)似乎只是一种推卸责任的做法。 - Gray
你为什么希望大多数的代码来处理它,而不是立即出错? - Louis Wasserman
因为我希望代码具有容错性。或者我正在编写一个供他人使用的库。有许多原因使我想要进行良好的防御性编程,而不是立即使我的应用程序出现错误。 - Gray

2

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

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

您可以省略异常抛出,但这会使您的代码 less robust :

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

假设添加了一个名为 fine 的枚举值,但您没有更新 getMessageForType() 函数,则将返回 "Info type" 而不是 "Fine type" 。因此,以下是更好的方法:
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;
  }
}

这种方法不再需要使用switchgetMessageForType()方法。
shouldReturnStringForEnum()方法可以非常简单:

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

0
你可以使用 switch 语句的 default 分支来返回默认值,例如:

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

但是这种方法的客户端会如何处理“未知的消息类型”呢?如果我想要一个未知的消息类型,我会在我的枚举值中表示它。我认为,在编译时没有可能的枚举值的情况下,抛出异常是更好的选择。 - agienka
当然,要么客户端有处理“未知消息类型”的方法,要么你可以抛出AssertionErrorRuntimeException或类似的异常... - deHaar

0
Java强制在枚举开关中使用默认返回语句,以覆盖所有枚举值...这种行为背后的设计决策是什么?
这是语言所做的非常重要(也正确)的决定。即使您当前的代码处理了枚举的所有当前值,也不能保证枚举类在编译代码之后不会发生变化。您可能会升级第三方库,它可能会添加另一个枚举值,导致您的代码在运行时无效而需要使用默认返回语句。
即使您控制枚举代码,这也并不意味着另一个开发人员(或未来的您)不会向枚举添加另一个值,并且未能更新switch语句。以这种方式编写具有向前兼容性的代码通常是一种最佳实践,在这种情况下,语言强制执行这种行为至关重要。
“我不知道在这里返回什么。”
问题就在于要不要抛出异常。如何处理无效的枚举值或其他异常情况是我们作为程序员每天都在努力解决的问题。在这种情况下,你需要问自己想要发生什么?这是否只是一个应该避免抛出的烦恼,还是更严重的错误?调用者是否应该处理异常,还是一个RuntimeException可以接受?这些都是你需要在应用程序上下文中回答的问题。
最后,我不知道你怎么看,但我认识的大多数程序员都会大量复制和粘贴代码。虽然这个枚举可能永远不会扩展,但未来的枚举将会扩展,适当处理这种情况可能会受益。
强制使用默认返回语句在这种情况下并不舒服... "未知类型"可能是一个不错的选择。
case ...:
     return ...;
default:
     // here in case someone updates the enum and forgets to update this code
     return "Unknown type";

在这种情况下抛出异常也没有意义。这有点取决于返回默认的“unknown”字符串有多重要。如果有一个新的枚举缺少条目,你想要它抛出异常还是不抛出异常?对于异常,您可能需要使用:
case ...:
     return ...;
default:
     throw new IllegalStateException("unknown enum type found for" + mType);

或者可能是IllegalArgumentException


我预计错误发生在我扩展枚举值后,没有在我的switch语句中涵盖它。根据这里的答案,我得出结论,在不扩展枚举的情况下,没有其他方法从此方法返回。而且这只是解释器的一个问题,它简单地不“知道”枚举值。 - agienka
抱歉,我无法真正解析您的评论。如果您已向枚举添加了元素(我认为此处不应使用“扩展”一词),那么您的代码肯定会编译成功。该类肯定“知道”枚举值,只是您的代码没有考虑未来的值。这与解释器无关。否则,我就不明白您的观点了。 - Gray

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