0

I need to fire different methods depending on the value of parent and child which are different values of the same enum type. Currently I do this:

switch (parent)
{
  case DEPARTMENT:
  {
    switch (child)
    {
      case TERMINAL:
      {
        event1();
        break;
      }
      case OPERATOR:
      {
        event2();
        break;
      }
    }
    break;
  }
  case OPERATOR:
  {
    switch (child)
    {
      case TERMINAL:
      {
        event3();
        break;
      }
    }
    break;
  }
}

The actual code contains 5-10 cases, with each case executing one or more long lines of code (methods with multiple arguments).

I tried populating a two-dimensional array with Runnables but it executed 2x slower.

Is there an alternative way of writing this, which would be more readable, yet nearly as fast?

Nicolas Raoul
  • 58,567
  • 58
  • 222
  • 373
Gili
  • 86,244
  • 97
  • 390
  • 689
  • Are lambdas that are coming in Java 8 acceptable? – nanofarad Oct 12 '13 at 00:30
  • Are `parent` and `child` the same enum type, or different types? – ajb Oct 12 '13 at 00:31
  • @ajb, the question already answers this. It says they are of the same enum type. – Gili Oct 12 '13 at 00:34
  • @hexafraction, ideally not (JDK8 can't be legally used in production yet) but post the answer and we'll let people vote :) – Gili Oct 12 '13 at 00:35
  • @Gili Can't today, sadly. Maybe someone else will post, and getmy upvote *hint, hint* – nanofarad Oct 12 '13 at 00:36
  • 3
    _"Please include a rough estimate of the performance difference between your answer and the two-dimensional switch (test it!). This will help us compare answers."_ Um, no, that's your responsibility. – Jim Garrison Oct 12 '13 at 00:43
  • This is more suited for [CodeReview.SE](http://codereview.stackexchange.com/). – asteri Oct 12 '13 at 01:53
  • Please comment when downvoting, otherwise I don't know the cause. – Gili Oct 12 '13 at 01:59
  • I find this to be an interesting and useful question. And yes, a good answer becomes a great answer by including performance measurements. – Nicolas Raoul Mar 10 '14 at 03:40
  • @NicolasRaoul thanks for the edits. Don't forget to upvote the question. Hopefully someone will come up with an even better answer than the one I've marked as accepted. – Gili Mar 10 '14 at 18:30

5 Answers5

1
class SwitchTable {
    private static class EventKey {
        private EnumType parent;
        private EnumType child;
        public EventKey (EnumType p, EnumType c) { parent=p; child=c; }
        public int HashCode () { ...something... }
    } 
    private HashMap<EventKey, Integer> events;
    public void setEvent (EnumType parent, EnumType child, int eventNumber) {
         ... add a map entry to events that maps new EventKey(parent,child) 
         ... to eventNumber
    }
    public int whichEvent (EnumType parent, EnumType child) {
         ... return the map entry for new EventKey(parent,child) or 0 if not found
    }
}

// do this once to set up
SwitchTable switches = new SwitchTable();
switches.setEvent (EnumType.DEPARTMENT, EnumType.TERMINAL, 1);
switches.setEvent (EnumType.DEPARTMENT, EnumType.OPERATOR, 2);
switches.setEvent (EnumType.OPERATOR, EnumType.TERMINAL, 3);

// then
switch (switches.whichEvent(parent, child)) {
    case 1:  event1();  break;
    case 2:  event2();  break;
    case 3:  event3();  break;
}

I'm too lazy to fill in all the details, but you get the idea. This should work even if the parent and child are different enum types. You could use a different implementation for SwitchTable (e.g. set up a 1- or 2-dimensional array to hold the event values instead of a HashMap). I haven't tested this and don't know how it compares speed-wise. I hope I didn't make any dumb syntax errors.

EDIT: whichEvent doesn't have to return an integer. You could make it a new enum type whose names reflect the kinds of actions you might want to take. That should improve readability.

ajb
  • 31,309
  • 3
  • 58
  • 84
1

An integer selector calculated from the combined values of parent and child will be faster:

public enum Stuff { DEPARTMENT, OPERATOR, TERMINAL };
Stuff parent = ...;
Stuff child  = ...;

int selector = parent.ordinal() * Stuff.values().length + child.ordinal();
switch(selector)
{
    case 0 : // parent=DEPARTMENT child=DEPARTMENT
        ...
    case 1 : // parent=DEPARTMENT child=OPERATOR
        ...
        ...
    case 3 : // parent=OPERATOR child=DEPARTMENT
        ...
    case 8:  // parent=TERMINAL child=TERMINAL
        ...
}

Some combinations may not be meaningful, just omit them and provide a default with nothing in it. You could also define constants in the enum:

private static final int n = values().length; // for brevity
public static final int DEPARTMENT_DEPARTMENT = DEPARTMENT.ordinal() * n + DEPARTMENT.ordinal()
        ...

and use those in the case statements.

Jim Garrison
  • 85,615
  • 20
  • 155
  • 190
  • 1
    Unfortunately, `DEPARTMENT_DEPARTMENT` is not a constant expression, so it cannot be used inside a switch statement. The numbers are less readable and reliable. – Gili Oct 12 '13 at 01:02
  • The results are in: this code is slightly faster (~5%) than the two-dimensional switch but I find readability to be worse due to the `constant expression` problem. My primary goal is to improve readability (and reliability in case enum values move around) so unfortunately I don't think this answer does it for me. – Gili Oct 12 '13 at 01:19
  • I've accepted your answer because it is the best of the bunch, but I ended up keeping the two-dimensional switch statement for readability (I know, it's a subjective decision). Thank you for this insightful approach. Hopefully I'll get to use it in the future. – Gili Oct 17 '13 at 19:47
1

Most of the cases, to archive readability compromises performance. Here is a solution works ONLY with java7.

public class SwitchArray {

    public enum Stuff {
        DEPARTMENT, OPERATOR, TERMINAL
    };

    static Stuff parent = Stuff.DEPARTMENT;
    static Stuff child = Stuff.OPERATOR;

    /**
     * @param args
     */
    public static void main(String[] args) {
        switch (SwitchArray.parent.toString() + "_" + SwitchArray.child.toString()) {
        case "DEPARTMENT_OPERATOR":
            System.out.println("hey!");
            break;
        case "DEPARTMENT_TERMINAL":
            System.out.println("ha!");
            break;
        default:
            break;
        }
    }

}
ren78min
  • 66
  • 6
  • Ouch! 26x slower than the original :) I'll say one thing for this question: it's teaching me a lot about Java performance I didn't know before. – Gili Oct 12 '13 at 03:20
0

Add a method to your enum (I'm calling it a MyEnum)

public void doFireEvent(MyEnum child) { // a single switch here for each enum }

Slightly cleaner IMO, at least it moves all that code to the enum class so you won't see it. :-)

(added later). A drawback of this is that your MyEnum class must have knowledge of / access to the event1() etc. methods, which may be impossible or inappropriate.

user949300
  • 15,364
  • 7
  • 35
  • 66
  • You can't do that, because each event depends on *two* enum values, not one. – Gili Oct 12 '13 at 01:32
  • Yes you can - call `parent.doFireEvent(child);` – user949300 Oct 12 '13 at 01:35
  • You're right, though as you mention this would require me to embed knowledge of event1() etc into the enum which doesn't make sense in my context. – Gili Oct 12 '13 at 01:57
  • Perhaps rename the method `figureEvent() or curryEvent()`, and have it return a single int or enum that caller can use in a single switch. Though at this point your original nested switch is looking pretty good! – user949300 Oct 12 '13 at 02:36
0

Your code can be made much more readable just by using better indentation and formatting techniques and also encapsulating all this dirty logic within the enum itself. It s really your formatting more than anything that makes it so difficult to trace.

public enum MyEnum {

    OPERATOR, TERMINAL;

    public static void execute(MyEnum parent, MyEnum child) {
        switch(parent) {
            case OPERATOR:
                switch(child) {
                    case OPERATOR: event1(); break;
                    case TERMINAL: event2(); break;
                }
            break;
            case TERMINAL:
                switch(child) {
                    case TERMINAL: event3(); break;
                }
            break;
        }
    }

}
asteri
  • 11,402
  • 13
  • 60
  • 84
  • The simplified question is misleading. The actual code contains 5-10 cases, with each case executing one or more long lines of code (methods with multiple arguments). I appreciated the suggestion though. – Gili Oct 12 '13 at 02:13