Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Collections for enums

When the keys of a map or the elements of a set are enum constants, EnumMap and EnumSet are a better choice than HashMap and HashSet. They are faster, use less memory and keep the constants in declaration order. The enum itself and its values(), valueOf() and ordinal() methods are covered in the enums lesson.

EnumMap

An EnumMap is a map whose keys are the constants of one enum. The enum class, for example Day.class, is passed to the constructor:

enum Day {
    MON, TUE, WED, THU, FRI, SAT, SUN
}
 
void main() {
    Map<Day, String> plans = new EnumMap<>(Day.class);
    plans.put(Day.FRI, "cinema");
    plans.put(Day.MON, "pool");
    plans.put(Day.WED, "English");
    IO.println(plans);
}

Output:

{MON=pool, WED=English, FRI=cinema}

The keys come in the order the constants were declared, not the order they were added. Inside an EnumMap is a plain array, so key lookup is very fast.

EnumSet

An EnumSet is a set of constants. It is not created with new but obtained from factory methods: of() — the listed constants, range() — all from one to another, allOf() — all of them, noneOf() — an empty set:

enum Day {
    MON, TUE, WED, THU, FRI, SAT, SUN
}
 
void main() {
    Set<Day> weekend = EnumSet.of(Day.SAT, Day.SUN);
    Set<Day> workdays = EnumSet.range(Day.MON, Day.FRI);
    IO.println(weekend);
    IO.println(workdays);
    IO.println(workdays.contains(Day.SAT));
}

Output:

[SAT, SUN]
[MON, TUE, WED, THU, FRI]
false

Internally an EnumSet stores the constants as the bits of a single number, like the masks from the bitwise operators lesson, but the code stays readable.

The complement of a set

EnumSet.complementOf() returns all the constants that are not in the given set. This is a handy way to say "all except":

enum Day {
    MON, TUE, WED, THU, FRI, SAT, SUN
}
 
void main() {
    EnumSet<Day> busy = EnumSet.of(Day.MON, Day.WED, Day.FRI);
    EnumSet<Day> free = EnumSet.complementOf(busy);
    IO.println(free);
}

Output:

[TUE, THU, SAT, SUN]

Counting by constant

An EnumMap is good for counting: the key is a constant, the value is a counter. The merge() method adds one, or creates the entry if it does not exist yet:

enum Status {
    NEW, PAID, SHIPPED
}
 
void main() {
    List<Status> orders = List.of(Status.PAID, Status.NEW, Status.PAID, Status.SHIPPED, Status.PAID);
    Map<Status, Integer> counts = new EnumMap<>(Status.class);
    for (Status s : orders) {
        counts.merge(s, 1, Integer::sum);
    }
    IO.println(counts);
}

Output:

{NEW=1, PAID=3, SHIPPED=1}

Integer::sum is a reference to a method that adds two numbers: the old value and one.

Finding a constant by a string

valueOf() crashes when there is no such constant. When the string comes from a user, it is safer to loop over values() and compare names ignoring case:

enum Size {
    SMALL, MEDIUM, LARGE
}
 
Size findSize(String text) {
    for (Size s : Size.values()) {
        if (s.name().equalsIgnoreCase(text)) {
            return s;
        }
    }
    return Size.MEDIUM;
}
 
void main() {
    IO.println(findSize("large"));
    IO.println(findSize("XXL"));
}

Output:

LARGE
MEDIUM

Common mistake

The ordinal() number depends on the order of the constants in the code. If you save it to a file or a database and later add a new constant at the beginning, the old numbers start meaning something else. Here 1 was saved when the enum was SMALL, MEDIUM, LARGE, and then XS was added at the front:

enum Size {
    XS, SMALL, MEDIUM, LARGE
}
 
void main() {
    int saved = 1;
    IO.println(Size.values()[saved]);
}

Output:

SMALL

Instead of MEDIUM we got SMALL. Save the constant's name (name()) or a field of your own with a code, and for per-constant data use an EnumMap instead of an array indexed by ordinal().

Thanks for reading!

Donate to the site — we work hard for you.