Java: Create a lookup table for an Enum 4


Simple Enum

Assuming the user has a simple enum that does not have any member variable as follows:

public enum Grades {
    A, B, C, D, E, F
}

In this case, we do not need to create a map to get an enum value from an input string. We can use the enum build-in method valueOf which returns the enum constant of the specified enum type with the specified name.

Please note that the input string must match exactly an identifier used to declare an enum constant in this type. Extraneous white-space characters or wrong letter case are not permitted.

An Enum that has a member variable

Switching to a more interesting example, we assume the user has an enum type that each enum constant has a member variable. In the following example each constant has an int value as member.

public enum Weekday {

    MONDAY(1),
    TUESDAY(2),
    WEDNESDAY(3),
    THURSDAY(4),
    FRIDAY(5),
    SATURDAY(6),
    SUNDAY(7);

    public int getCode() {

        return mCode;
    }

    Weekday(final int code) {

        mCode = code;
    }

    private final int mCode;
}

In this example, getting the matching enum constant for an input integer number is not as straight forward as before. We could iterate over all constants check their code and return the one that matches but that would not be efficient, especially for enum types that have many constants.

What we propose is to create a static map of elements that will have on the key part the value of the enum constant and on the value part the actual enum constant.

public enum Weekday {

    MONDAY(1),
    TUESDAY(2),
    WEDNESDAY(3),
    THURSDAY(4),
    FRIDAY(5),
    SATURDAY(6),
    SUNDAY(7);

    public int getCode() {

        return mCode;
    }

    public static Weekday fromCode(final int code) {

        final Weekday element = sMap.get(code);
        if (element != null) {

            return element;
        }
        throw new IllegalArgumentException(String.format("Unknown code: <%d> for %s", code, Weekday.class.toString()));
    }

    Weekday(final int code) {

        mCode = code;
    }

    private static final Map<Integer, Weekday> sMap = new HashMap<>();

    static {
        for (final Weekday d : Weekday.values()) {

            sMap.put(d.getCode(), d);
        }
    }
    private final int mCode;
}

What we did in the above code, was to statically create the map we described. Which is, a map from the value that each enum constant holds to the constant. Then using the fromCode method we defined, we check the map for the input value, if the value is not part of the keys set of the map, it will throw an Illegal Argument Exception.

Doing Even better

An improvement we could do in the above code, would be to extract the part of the code that iterates over the fields and creates the map to reuse it. To do that, we need to use Functional Interface. Meaning we will create a function that takes as input another function that expects some input (if any) and produces as output an element of the type of the key.

Our function that can be used for all enums that their constants hold an int value is the following:

//ToIntFunction represents a function that produces an int-valued result. This is the int-producing primitive specialization for Function. This is a functional interface whose functional method is applyAsInt(Object).
public static <E extends Enum<E>> Map<Integer, E> createMap(final ToIntFunction<E> converter, final Class<E> enumClass) {

    final Map<Integer, E> map = new HashMap<>();
    for (final E s : enumClass.getEnumConstants()) {

        map.put(converter.applyAsInt(s), s);
    }
    return map;
}

The code of the enum could change to the following:

public enum Weekday {

    MONDAY(1),
    TUESDAY(2),
    WEDNESDAY(3),
    THURSDAY(4),
    FRIDAY(5),
    SATURDAY(6),
    SUNDAY(7);

    public int getCode() {

        return mCode;
    }

    public static Weekday fromCode(final int code) {

        final Weekday element = sMap.get(code);
        if (element != null) {

            return element;
        }
        throw new IllegalArgumentException(String.format("Unknown code: <%d> for %s", code, Weekday.class.toString()));
    }

    Weekday(final int code) {

        mCode = code;
    }

    private static final Map<Integer, Weekday> sMap = Collections.unmodifiableMap(createMap(Weekday::getCode, Weekday.class));

    private final int mCode;
}

The function that we used as input to the new function is the getCode we defined in the enum. As you can see, it is a function that produces an int-valued result just as the requirement of the ToIntFunction parameter asks for.


Leave a Reply

4 thoughts on “Java: Create a lookup table for an Enum