Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Enum based table/matrix in Java

Tags:

java

enums

matrix

I've got two enums: level with 3 values and criticality with 4 values. A combination of those two maps to one of 8 values from priority enum. The mapping is non-linear and may change in future.

What is the best* way to implement a static function that takes level and criticality and outputs a priority?

*best being easy to read and understand, easy and safe to change, and not a performance hog. Extra points for a solution that takes into account that input domain can change in the future.

Ways I considered so far:

nested switch..case. Many lines and lots of boilerplate code. Also error-prone if you forget to return a value in a case. Basically the code looks like this:

    switch (bc) {
        case C1:
            switch (el) {
                case E1:
                    return EmergencyPriority.P1;
                case E2:
                    return EmergencyPriority.P2;
                case E3:
                    return EmergencyPriority.P3;
            }
        case C2:
            switch (el) {
                case E1:
                    return EmergencyPriority.P2;
                case E2:
                    return EmergencyPriority.P3;
                case E3:
                    return EmergencyPriority.P4;
            }
        case C3:
            switch (el) {
                case E1:
                    return EmergencyPriority.P4;
                case E2:
                    return EmergencyPriority.P5;
                case E3:
                    return EmergencyPriority.P6;
            }
        case C4:
            switch (el) {
                case E1:
                    return EmergencyPriority.P6;
                case E2:
                    return EmergencyPriority.P7;
                case E3:
                    return EmergencyPriority.P8;
            }
    }

Mutikey Map requires an external library and I haven't found a way to nicely insert initial values without many function calls and boilerplate composite keys.


if..else if.. else basically same as switch case but with more boilerplate code. Less error-prone though.


Two dimensional array when using the enum values as integers for array indices you risk failing silently if the positional enum values change.


Your solution here

like image 599
Hubert Grzeskowiak Avatar asked Apr 04 '16 16:04

Hubert Grzeskowiak


1 Answers

This structure is probably the "best" way to store your data ("best" = what I'm assuming your after, because I'd be perfectly fine with your switch based solution)

1. An EnumMap based solution

EnumMap<Level, EnumMap<Criticality, Priority>> map = new EnumMap<>(Level.class);
EnumMap<Criticality, Priority> c1 = new EnumMap<>(Criticality.class);
c1.put(Criticality.E1, Priority.P1);
..
map.put(Level.C1, c1);
...

Then, simply write this utility method to access the structure:

public static Priority priority(Level level, Criticality criticality) {
    return map.get(level).get(criticality);
}

The advantage of EnumMap is: It offers Map convenience while being rather efficient, as all the possible keys are known in advance, so values can be stored in an Object[].

2. An array based solution

You've already mentioned this, but I'll still repeat the idea, because I've done this in the past, and with proper formatting (that must never be broken by devs, of course), this approach is very readable and not very error prone.

Remember, formatting is key here:

Priority[][] map = {
  //               Criticality.E1   Criticality.E2   Criticality.E3
  // ----------------------------------------------------------------
  /* Level.C1 */ { Priority.P1    , Priority.P2    , Priority.P3    },
  /* Level.C2 */ { Priority.P2    , Priority.P3    , Priority.P4    },
  /* Level.C3 */ { Priority.P3    , Priority.P4    , Priority.P5    },
  /* Level.C4 */ { Priority.P4    , Priority.P5    , Priority.P6    }
};

And now, the method looks like this:

public static Priority priority(Level level, Criticality criticality) {
    return map[level.ordinal()][criticality.ordinal()];
}

In order to prevent failing silently in case someone adds a new enum value in the middle, just add a unit test that asserts the expected ordinal for each enum literal. The same test can also assert the Level.values().length and Criticality.values().length values, and you're safe for the future.

like image 182
Lukas Eder Avatar answered Sep 20 '22 01:09

Lukas Eder