daemon_headmaster
daemon_headmaster

Reputation: 819

Why is a switch case statement unable to work with properties of an Enum?

I am writing a simple game in which I use an Enum, CommandManager, to store information about the possible commands and what each one does. The main purposes of this Enum is to be able to print out a menu of available commands, as well as being used to check input and performing the action related to that input. My problem lies with that second use, where I am using a switch statement to determine what the user wants to do based on their input. I'm getting a compilation error when trying to use a property of an Enum (via a getter method) as a case label. The error message provided is that case expressions must be constant expressions. Given that the properties of CommandManager are declared to be final, am I right in thinking that properties of Enums simply cannot be used in switch statements? If this is the case, why?

Simplified version of the code included below in case it's an error on my end.

Method Code:

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;
            }
    }
}

Enum code:

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;
}
}

Upvotes: 3

Views: 2948

Answers (4)

Stephen C
Stephen C

Reputation: 718778

Am I right in thinking that properties of Enums simply cannot be used in switch statements?

You are.

If this is the case, why?

Because the "labels" for a switch statement need to be compile time constants, and the properties of an enum do not qualify.

The reason that they need to be compile time constants is that the compiler needs to check that the switch labels are distinct. It cannot allow something like this

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

where A.method() and B.method() turn out to have the same value. If the case expressions are not compile-time constant expressions, then the compiler cannot detect the problem. (Method calls are never compile time constant expressions.)

Upvotes: 5

Dmitry Smorzhok
Dmitry Smorzhok

Reputation: 635

You have to use a bit different approach. You can write a static method inside your enum to convert command string into CommandManager enum:

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
}

Then you can invoke this method to get en enum object and use a switch statement to do whatever you want:

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);
    }
}

Upvotes: 1

dwangel
dwangel

Reputation: 41

Simply "CommandManager.MAINMENU.getCommand()" is not a Constant, is a function. Element in enum is Constant, so you have to convert String to enum element.

I used a map to store relation between command and enum element:

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;
  }
}

Then change the switch code

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;
      }
    }
  }
}

Upvotes: 1

Hovercraft Full Of Eels
Hovercraft Full Of Eels

Reputation: 285403

I would do things differently by using a Map to help with an action design pattern.

First add to your CommandManager enum a method that converts a command String to a CommandManager object, something like:

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

e.g.,

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);
    }
}

Then give your code that uses this a Map that maps each CommandManager with a Runnable, and fill the map:

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());
    }

Then use it!

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

Upvotes: 2

Related Questions