【问题标题】:Why is a switch case statement unable to work with properties of an Enum?为什么 switch case 语句不能使用 Enum 的属性?
【发布时间】:2017-02-17 09:56:00
【问题描述】:

我正在编写一个简单的游戏,其中我使用枚举 CommandManager 来存储有关可能的命令以及每个命令的作用的信息。此枚举的主要目的是能够打印出可用命令的菜单,以及用于检查输入并执行与该输入相关的操作。我的问题在于第二次使用,我使用 switch 语句根据用户的输入来确定用户想要做什么。尝试使用 Enum 的属性(通过 getter 方法)作为案例标签时出现编译错误。提供的错误消息是case expressions must be constant expressions。鉴于CommandManager 的属性被声明为最终属性,我是否认为枚举的属性根本不能在 switch 语句中使用?如果是这样,为什么?

下面包含的代码的简化版本,以防我的错误。

方法代码:

void interpretInput()   {
    String command = input.getInput();
    if (command.length() == 2)  {
            switch (command) {
            case CommandManager.MAINMENU.getCommand(): goToMainMenu(); 
                    break;
            case CommandManager.NEWGAME.getCommand(): startNewGame();
                    break;
            case CommandManager.LISTGAMES.getCommand(): listSavedGames();
                    break;
            case CommandManager.EXITGAME.getCommand(): exitGame();
                    break;
            case CommandManager.HELPMENU.getCommand(): listAllCommands();
                    break;
            }
    }
}

枚举代码:

public enum CommandManager {

NEWGAME("!n", "New game"),
MAINMENU("!m", "Go to main menu"),
EXITGAME("!q", "Exit Battleships"),
LISTGAMES("!g", "List saved games"),
HELPMENU("!h", "Open help menu"),
LOADGAME("!l", "Load a new game"),
SAVEGAME("!s", "Save current game");

private final String command;
private final String menuOption;

CommandManager(String aCommand, String anOption)    {
    command = aCommand;
    menuOption = anOption;
}

String getCommand() {
    return command;
}

String getMenuOption()  {
    return menuOption;
}
}

【问题讨论】:

  • 因为它们不是编译时间常数。
  • 你可以在 switch 语句中使用枚举,但你不能。您正在使用字符串,但正如错误消息所述,标签必须是常量。
  • 从输入中读取String command后,得到对应的枚举值,然后直接开启枚举就好了。
  • 如果您切换到 String 值,您并没有很好地使用 enum。如果这是您的目标,则有许多 public static final String 常量。您将能够对它们进行切换。
  • 如果您正在编写 enum 类,那么在其上使用 switch 通常是错误的,因为您可以为枚举常量编写特定于常量的方法,这些方法几乎在每种情况下都提供了更多对象面向、健壮且更易于维护的解决方案。实际上,几乎在每种情况下,您在代码中写入 switch 后的第一个反应应该是问自己“有没有一种方法可以让我无法切换?”

标签: java enums switch-statement


【解决方案1】:

我会通过使用地图来帮助操作设计模式来做不同的事情。

首先向您的 CommandManager 枚举添加一个将命令字符串转换为 CommandManager 对象的方法,例如:

public static CommandManager getCommandManager(String command) {
    for (CommandManager cManager : CommandManager.values()) {
        if (command.equals(cManager.getCommand())) {
            return cManager;
        }
    }
    throw new IllegalArgumentException(command);
}

例如,

public enum CommandManager {

    NEWGAME("!n", "New game"), 
    MAINMENU("!m", "Go to main menu"), 
    EXITGAME("!q", "Exit Battleships"), 
    LISTGAMES("!g", "List saved games"), 
    HELPMENU("!h", "Open help menu"), 
    LOADGAME("!l", "Load a new game"), 
    SAVEGAME("!s", "Save current game");

    private final String command;
    private final String menuOption;

    CommandManager(String aCommand, String anOption) {
        command = aCommand;
        menuOption = anOption;
    }

    String getCommand() {
        return command;
    }

    String getMenuOption() {
        return menuOption;
    }

    // ************ ADD THIS! *******
    public static CommandManager getCommandManager(String command) {
        for (CommandManager cManager : CommandManager.values()) {
            if (command.equals(cManager.getCommand())) {
                return cManager;
            }
        }
        throw new IllegalArgumentException(command);
    }
}

然后给你的代码使用这个 Map 映射每个 CommandManager 到一个 Runnable,然后填充这个映射:

public class TestEnum {
    private Map<CommandManager, Runnable> commandMap = new EnumMap<>(CommandManager.class);

    public TestEnum() {
        commandMap.put(CommandManager.MAINMENU, () -> goToMainMenu());
        commandMap.put(CommandManager.MAINMENU, () -> goToMainMenu());
        commandMap.put(CommandManager.NEWGAME, () -> startNewGame());
        commandMap.put(CommandManager.LISTGAMES, () -> listSavedGames());
        commandMap.put(CommandManager.EXITGAME, () -> exitGame());
        commandMap.put(CommandManager.HELPMENU, () -> listAllCommands());
    }

那就用吧!

void interpretInput(String command) {
    CommandManager cManager = CommandManager.getCommandManager(command);
    commandMap.get(cManager).run();
}

【讨论】:

    【解决方案2】:

    您必须使用一些不同的方法。您可以在枚举中编写一个静态方法将命令字符串转换为 CommandManager 枚举:

    public static CommandManager fromCommand(String command) {
        for (CommandManager commandManager : values()) {
            if (commandManager.getCommand().equals(command)) {
                return commandManager;
            }
        }
        return null; // or throw exception, whatever fits best for your code
    }
    

    然后你可以调用这个方法来获取 enum 对象并使用 switch 语句来做任何你想做的事情:

    String command = input.getInput();
    CommandManager commandManager = CommandManager.fromCommand(command);
    if (commandManager != null)  {
        switch (commandManager) {
            case MAINMENU: 
                goToMainMenu();
                break;
            case NEWGAME: 
                startNewGame();
                break;
            case LISTGAMES: 
                listSavedGames();
                break;
            case EXITGAME: 
                exitGame();
                break;
            case HELPMENU: 
                listAllCommands();
                break;
            default:
                throw new IllegalArgumentException("Unknown command: " + commandManager);
        }
    }
    

    【讨论】:

    • 这个答案和@HovercraftFullOfEels 的答案对我来说很有意义并解决了我的问题,所以我将其标记为解决方案。感谢您的帮助,以及所有提供答案的人 - 有很多有趣的东西供我阅读。
    【解决方案3】:

    简单地说“CommandManager.MAINMENU.getCommand()”不是一个常数,是一个函数。 枚举中的元素是常量,所以你必须将字符串转换为枚举元素。

    我使用映射来存储命令和枚举元素之间的关系:

    public enum CommandManager {
    
      NEWGAME("!n", "New game"),
      MAINMENU("!m", "Go to main menu"),
      EXITGAME("!q", "Exit Battleships"),
      LISTGAMES("!g", "List saved games"),
      HELPMENU("!h", "Open help menu"),
      LOADGAME("!l", "Load a new game"),
      SAVEGAME("!s", "Save current game");
    
      private final String command;
      private final String menuOption;
      private static class InnerClass {
        static Map<String, CommandManager> commandMap = new HashMap<>();
      }
    
      CommandManager(String aCommand, String anOption)    {
        command = aCommand;
        menuOption = anOption;
        InnerClass.commandMap.put(aCommand, this);
      }
    
      public static CommandManager parseCommand(String aCommand) {
        return InnerClass.commandMap.get(aCommand);
      }
    
      String getCommand() {
        return command;
      }
    
      String getMenuOption()  {
        return menuOption;
      }
    }
    

    然后改开关码

    public void interpretInput()   {
      String command = input.getInput();
      if (command.length() == 2)  {
        CommandManager commandManager = CommandManager.parseCommand(command);
        if (commandManager!=null) {
          switch (commandManager) {
            case MAINMENU: goToMainMenu();
              break;
            case NEWGAME: startNewGame();
              break;
            case LISTGAMES: listSavedGames();
              break;
            case EXITGAME: exitGame();
              break;
            case HELPMENU: listAllCommands();
              break;
          }
        }
      }
    }
    

    【讨论】:

      【解决方案4】:

      我认为枚举的属性根本不能在 switch 语句中使用是对的吗?

      你是。

      如果是这样,为什么?

      因为 switch 语句的“标签”需要是编译时间常量,而enum 的属性不符合条件。

      它们需要成为编译时常量的原因是编译器需要检查开关标签是否不同。它不能允许这样的事情

      switch (someValue) {
          case A.method():  doA();
          case B.method():  doB();
      }
      

      A.method()B.method() 的值相同。如果 case 表达式不是编译时常量表达式,则编译器无法检测到问题。 (方法调用永远不是编译时常量表达式。)

      【讨论】:

      • 感谢您清楚地解释我为什么会收到错误。
      猜你喜欢
      • 1970-01-01
      • 2011-10-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-04-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多