Max Rhan
Max Rhan

Reputation: 345

Create a enum factory method out of a unique instance value

I have created an Enum to define certain actions. Programming against a external API I am forced to use an Integer to express this action. That's why I have added an integer instance field to my Enum. This should be d'accord with Joshua Bloch's Effective Java, instead of relying on ordinal() or the order of the Enum constants using values()[index].

public enum Action {

    START(0),

    QUIT(1);

    public final int code;

    Protocol(int code) {
         this.code = code;
     }
}

I get an integer value what from the API and now I want to create an Enum value out of it, how can I implement this in the most generic fashion?

Obviously, adding such a factory method, will not work. You cannot instantiate an Enum.

Action valueOf(int what) {
     return new Action(what);
}

Of course, I can always make a switch-case statement and add all the possible codes and return the appropriate constant. But I want to avoid defining them in two places at the same time.

Upvotes: 5

Views: 1396

Answers (3)

Brian
Brian

Reputation: 17319

If you're going to have a lot of them, you can use a HashMap<Integer, Action>:

private static final Map<Integer, Action> actions = new HashMap<>(values().size, 1);

static {
    for (Action action : values())
        actions.put(action.code, action);
}

// ...

public static Action valueOf(int what) {
    return actions.get(what);
}

This is useful if you're going to have a large number of Action values since the HashMap lookup is O(1).

Upvotes: 5

assylias
assylias

Reputation: 328775

I would personally keep it simple (YAGNI) and use the ordinal value but:

  • I would keep the logic within the enum to make sure outside code does not know about that implementation detail and does not rely on it
  • I would make sure I have a test that fails if something breaks (i.e. if the numbers don't start from 0 or are not incremental)

enum code:

public enum Action {

    START(0),
    QUIT(1);
    private final int code;

    Action(int code) {
        this.code = code;
    }

    public int getCode() {
        return code;
    }

    public static Action of(int code) {
        try {
            return Action.values()[code];
        } catch (IndexOutOfBoundsException e) {
            throw new IllegalArgumentException("not a valid code: " + code);
        }
    }
}

test

@Test
public testActionEnumOrder() {
    int i = 0;
    for (Action a : Action.values()) {
        assertEquals(a.getCode(), i++);
    }
}

If you change QUIT(1) to QUIT(2) for example, the test will fail. When that happens, you can use a HashMap or a lookup loop.

Upvotes: 0

Ian Roberts
Ian Roberts

Reputation: 122414

If you are sure that your codes will always be sequential and starting from 0 then the most efficient option would be

public enum Action {
    START(0),

    QUIT(1);

    public static final Action[] ACTIONS;
    static {
      ACTIONS = new Action[values().length];
      for(Action a : values()) {
        ACTIONS[a.code] = a;
      }
    }

    public final int code;

    Protocol(int code) {
         this.code = code;
     }
}

Upvotes: 1

Related Questions