Safe use of Enum valueOf for String comparison on a switch

Viewed 13561

How to safe implement the use of valueOf in case i get a String different than the supported on the enum ACTION. I mean is possible to force to ACTION.valueOf(valueToCompare) to get a valid value even when happens that valueToCompare is not a valid enum member

I get an expected execution when valueToCompare is "CRY" or "CRYALOT" or "cryalot" etc.

And i get java.lang.IllegalArgumentException on cases like in the code.

public enum ACTION{
    CRY,
    CRYALOT;
}

public static void main(String[] args) {

    String valueTocompare = "posible not expected value".toUpperCase();

    switch (ACTION.valueOf(valueToCompare)) {
        case CRY:
            System.out.println("Cry");
            break;
        case CRYALOT:
            System.out.println("Cry a lot");
            break;
        default:
            System.out.println("catch posible not expected value");
            break;
    }
}

EDIT & used SOLUTION:

I solved this by using a try-catch as @Peter Lawrey suggested:

public enum ACTION{
    CRY,
    CRYALOT,
    NOTVALID;
}

public static void main(String[] args) {

    String valueToCompare = "NOTVALID";
    ACTION action;
    try {
        valueToCompare= "variable posible not expected value".toUpperCase();
        action = ACTION.valueOf(valueToCompare);
    } catch(IllegalArgumentException e){
        System.out.println("Handled glitch on the Matrix");
        action = ACTION.NOTVALID;
    }

    switch (action) {
        case CRY:
            System.out.println("Cry");
            break;
        case CRYALOT:
            System.out.println("Cry a lot");
            break;
        default:
            System.out.println("catch posible not expected value");
            break;
    }

    System.out.println("We continue normal execution on main thread...");
}
6 Answers

You need to catch the IllegalArgumentException

try {
    switch (ACTION.valueOf(valueToCompare)) {

    }
} catch (IllegalArgumentException iae) {
    // unknown
}

Or you can create your own function which does this.

public static <E extends Enum<E>> E valueOf(E defaultValue, String s) {
    try {
        return Enum.valueOf(defaultValue.getDeclaringClass(), s);
    } catch (Exception e) {
        return defaultValue;
    }
}

Note: switch(null) throws a NullPointerException rather than branching to default:

Using exceptions for flow control is considered as a bad practice.

    String valueToCompare = value.toUpperCase();

    ACTION action = Arrays.stream(ACTION.values())
       .filter(a -> a.name().equals(valueToCompare)).findFirst().orElse(ACTION.NOTVALID);

the problem here is this line: ACTION.valueOf(valueToCompare) - you are trying to run valueOf on valueToCompare, and its erroring out since the value isn't an enum in ACTION. It's not even making the switch statement to print out the default msg.

Have a look at the changes I've done, you'll notice a few things, the main one being actionToCompare...

    enum Action {
      CRY,
      CRYALOT,
      EXAMPLE
    }

  public static void main(String[] args) {
    Action actionToCompare = Action.EXAMPLE;
    switch (actionToCompare) {
      case CRY:
        System.out.println("Cry");
        break;
      case CRYALOT:
        System.out.println("Cry a lot");
        break;
      default:
        System.out.println("catch posible not expected value");
        break;
    }
  }

if you insist on using a String over converting it to the enum Action, wrap it in a try...catch statement so if an invalid string is passed in it can handle the error.

You can always build yourself a reverse-lookup.

enum Action {
    CRY,
    CRYALOT,
    EXAMPLE;

    // Lookup map for actions in string form.
    static Map<String,Action> lookup = Arrays.stream(values()).collect(Collectors.toMap(
            // Key is name in lowercase.
            a -> a.name().toLowerCase(),
            // Value is the Action.
            a -> a));

    public static Action lookup(String name) {
        return lookup.get(name.toLowerCase());
    }
}

public void test() throws Exception {
    System.out.println(Action.lookup("cry"));
    System.out.println(Action.lookup("CryAlot"));
}

A solution that does not involve exceptions in the control flow and enables mapping the enum name or the action name to the action with a default behavior in case there is no mapping entry:

public enum CryActions {
    CRY_A_LITTLE("Cry a little", CryALittleActionHandler::new), CRY_A_LOT("Cry a lot", CryALotActionHandler::new), DEFAULT("Default", DefaultCryActionHandler::new);

    private String actionName;
    private Supplier<CryActionHandler> supplier;

    private CryActions(String actionName, Supplier<CryActionHandler> supplier) {
        this.actionName = actionName;
        this.supplier = supplier;
        PossibleCryActions.byEnumName.put(name(), this);
        PossibleCryActions.byActionName.put(actionName, this);
    }

    public void handleAction() {
        supplier.get().handleAction();
    }

    public String getActionName() {
        return actionName;
    }

    public static CryActions fromEnumName(String enumName) {
        return PossibleCryActions.byEnumName.computeIfAbsent(enumName, x -> DEFAULT);
    }

    public static CryActions fromActionName(String actionName) {
        return PossibleCryActions.byActionName.computeIfAbsent(actionName, x -> DEFAULT);
    }

    private static class PossibleCryActions {
        private static Map<String, CryActions> byEnumName = new HashMap<>();
        private static Map<String, CryActions> byActionName = new HashMap<>();
    }
}

public interface CryActionHandler {
    void handleAction();
}

public class CryALittleActionHandler implements CryActionHandler {

    @Override
    public void handleAction() {
        System.out.println("Just crying a little...");
    }
}

public class CryALotActionHandler implements CryActionHandler {

    @Override
    public void handleAction() {
        System.out.println("Crying a river...");
    }
}

public class DefaultCryActionHandler implements CryActionHandler {

    @Override
    public void handleAction() {
        System.out.println("Just crying as default behavior...");
    }
}

public class Main {
    public static void main(String[] args) {
        CryActions.fromEnumName("CRY_A_LITTLE").handleAction();
        CryActions.fromEnumName("CRY_A_LOT").handleAction();
        CryActions.fromEnumName("CRY_UNEXPECTEDLY").handleAction();
        CryActions.fromActionName("Cry a little").handleAction();
        CryActions.fromActionName("Cry a lot").handleAction();
        CryActions.fromActionName("Cry unexpectedly").handleAction();
    }
}

Kotlin version:

inline fun <reified T : Enum<T>> safeValueOf(name: String, defaultValue: T): T =
try {
    java.lang.Enum.valueOf(T::class.java, name) ?: defaultValue
} catch(e: IllegalArgumentException) {
    defaultValue
}

Related