views:

894

answers:

7

I'm very familiar with using Enums in other languages, but I'm having some difficulty in Java with a particular use.

The Sun documentation for Enums boldly states:

"Java programming language enums are far more powerful than their counterparts in other languages, which are little more than glorified integers."

Well, that's dandy, but I kind of need to have a constant datatype representation for each of the Enums, for comparison reasons in a switch statement. The situation is as follows: I'm constructing nodes which will represent a given space, or 'slot' in a maze graph, and these nodes must be able to be constructed from a 2D integer array which represents the maze. Here's what I've got for the MazeNode class, which is currently where the problem is (the switch statement barks):

NOTE: I know this code does not function, due to the dynamic item in the case statement. It is there to illustrate what I'm after.

public class MazeNode
{
    public enum SlotValue
    {
        empty(0),
        start(1),
        wall(2),
        visited(3),
        end(9);

        private int m_representation;

        SlotValue(int representation)
        {
            m_representation = representation;
        }

        public int getRepresentation()
        {
            return m_representation;
        }
    }

    private SlotValue m_mazeNodeSlotValue;

    public MazeNode(SlotValue s)
    {
        m_mazeNodeSlotValue = s;
    }

    public MazeNode(int s)
    {

        switch(s)
        {
            case SlotValue.empty.getRepresentation():
                m_mazeNodeSlotValue = SlotValue.start;
                break;
            case SlotValue.end.getRepresentation():
                m_mazeNodeSlotValue = SlotValue.end;
                break;

        }
    }

    public SlotValue getSlotValue()
    {
        return m_mazeNodeSlotValue;
    }

}

So the code complains on the switch statement with "case expressions must be constant expressions" -- I can see why the compiler might have trouble, since technically they are dynamic, but I'm not sure what approach to take to resolve this. Is there a better way?

The bottom line is I need the Enum to have corresponding integer values for comparison against the incoming 2D array of integers in the program.

A: 

All languages (with the exception of JS because it's insane :D ) require switch statements to be constant expressions.

What are you trying to do? You can easily go from SlotValue to int by adding a getter to SlotValue.

olliej
Yes I could add a getter, but that doesn't address the issue: having something to compare against in a switch statement operating on a specific SlotValue Enum type. This is where the java is complaining.
byte
I mean, I could very well just have the case statements be the raw integer values, but that kind of defeats the purpose of it being an Enum, as opposed to an arbitrary set of integers. I'm looking for a way to map Enum values to integers.
byte
Updated code to reflect the issue more clearly, with a getter. Obviously one cannot use a method as a case statement in a switch - but I'm providing that as illustration of what I'm after.
byte
Side note: PHP is also 'insane' by your standard here... heh. I know what you mean though.
byte
+1  A: 

It appears that there's no easy way to do what you want. Constants must be both final and assigned when they're declared; something you cannot do in the member of an enum.

By way of a solution I've added a static decode() method to the SlotValue enum. This does a comparison of each SlotValue's m_representation field and returns the first match. It'll work, and it might not be the most efficient approach, but it does the job in only a few lines of code.

public class MazeNode {
    public enum SlotValue {
        empty(0),
        start(1),
        wall(2),
        visited(3),
        end(9);

        private int m_representation;

        SlotValue(int representation) {
            m_representation = representation;
        }

        private static SlotValue decode( int in ) {
            for ( SlotValue slot : values() ) {
                if ( slot.m_representation == in ) {
                    return slot;
                }
            }
            return empty;
        }
    }

    private SlotValue m_mazeNodeSlotValue;

    public MazeNode(SlotValue s) {
        m_mazeNodeSlotValue = s;
    }

    public MazeNode(int s) {
        m_mazeNodeSlotValue = SlotValue.decode( s );
    }

    public SlotValue getSlotValue() {
        return m_mazeNodeSlotValue;
    }
}
banjollity
Aware that case statements must be constant, but your proposed solution doesn't solve the error. The switch statement still says complains about the case items not being constant.
byte
By jove you're right. I'll edit my answer with something better.
banjollity
Heh, thanks - that's what the others have proposed above, and I think I'll use it.
byte
+1  A: 

I'm with olliej that you should probably accept a getter. Java (unlike C#) does not allow you to cast between a primitive (int in your case) and a enum. The internal representation (m_representation here) is just another field, not an accessible constant.

This is good (genuinely type-safe) or bad (harder to serialize and deserialize, among other things) depending how you look at it. The method below obviously is not as efficient as a switch, but I believe it's the only way to avoid redundancy.

As you probably realize, it's best to keep the data in enum form as much as possible.

public enum SlotValue
{
    empty(0),
    start(1),
    wall(2),
    visited(3),
    end(9);

    private int m_representation;

    SlotValue(int representation)
    {
        m_representation = representation;
    }

    public static SlotValue fromInt(int intSerialization)
    {
        for(SlotValue sv : SlotValue.values())
            if(sv.m_representation == intSerialization)
                return sv;
        return null;
    }
}

private SlotValue m_mazeNodeSlotValue;

public MazeNode(SlotValue s)
{
    m_mazeNodeSlotValue = s;
}

public MazeNode(int s)
{
    m_mazeNodeSlotValue = SlotValue.fromInt(s);
}
Matthew Flaschen
There's a little more here than just a getter... but it works, so I thank you sir.
byte
+3  A: 

You can use something like this:

import java.util.HashMap;
import java.util.Map;

public class MazeNode {

    public enum SlotValue {
     empty(0), start(1), wall(2), visited(3), end(9);

     protected int m_representation;

     SlotValue(int representation) {
      m_representation = representation;

     }

     private static final Map<Integer, SlotValue> mapping = new HashMap<Integer, SlotValue>();

     static {
      for (SlotValue slotValue : SlotValue.values()) {
       mapping.put(slotValue.m_representation, slotValue);
      }
     }

     public static SlotValue fromRepresentation(int representation) {
      SlotValue slotValue = SlotValue.mapping.get(representation);
      if (slotValue == null)
       // throw your own exception
       throw new RuntimeException("Invalid representation:" + representation);
      return slotValue;
     }
    }

    private SlotValue m_mazeNodeSlotValue;

    public MazeNode(SlotValue s) {
     m_mazeNodeSlotValue = s;
    }

    public MazeNode(int s) {
     m_mazeNodeSlotValue = SlotValue.fromRepresentation(s);

    }

    public SlotValue getSlotValue() {
     return m_mazeNodeSlotValue;
    }

    public static void main(String[] args) {
     MazeNode m = new MazeNode(2);
     System.out.println(m.getSlotValue());
     m = new MazeNode(9);
     System.out.println(m.getSlotValue());
    }

}
Gábor Hargitai
This is more or less what Matthew said above, which sounds like a good solution. Thank you
byte
Stolen exception throwing from Ray Tayek's solution.
Gábor Hargitai
Nice. You can actually move the mapping and static constructor inside the enum.
Matthew Flaschen
Yes, you are right, Thanks.
Gábor Hargitai
A: 

I am not sure what's the purpose of SlotValue in your program but it seems that you are not using their full power. You can invoke methods on enum instances or query their state. Thus you translate a switch on enum value into method invocation/state querying, as show below.

Side note: Once you get used to this kind of thinking (which is quite different from the thinking induced by C's enums) you realize that there's much less need for "magic numbers" in your code. Instead of specifying an value and then giving it some meaning you just specify the enum constant and invoke methods on it. Specifcially, my feeling is that there's no real need for associating each of your enum instances with a value (empty is 0, start is 1, etc.).

Anyway, here's the code:

public class MazeNode
{
   public enum SlotValue
   {
       empty(0),
       start(1),
       wall(2),
       visited(3),
       end(9);

       private int m_representation;

       SlotValue(int representation)
       {
           m_representation = representation;
       }

       public int getRepresentation()
       {
           return m_representation;
       }

       private SlotValue next = null;

       static
       {
          empty.next = start;
          end.next = end;
       }
   }


   private SlotValue m_mazeNodeSlotValue;

   public MazeNode(SlotValue s)
   {
       m_mazeNodeSlotValue = s;
   }

   public MazeNode(int s)
   {
       m_mazeNodeSlotValue = SlotValue.values()[s].next;
   }

   public SlotValue getSlotValue()
   {
       return m_mazeNodeSlotValue;
   }
}
Itay
This is wrong. For "end", you'll get SlotValue.values()[9] in the MazeNode constructor, which will give you a ArrayIndexOutOfBoundsException.
Matthew Flaschen
A: 

consider doing something like this:

public class Node {
    public enum Slot {
     empty, start, wall, visited, end;
     static Slot fromInt(int s) {
      for (Slot slot : Slot.values())
       if (slot.ordinal() == s)
        return slot;
      throw new RuntimeException("" + s + " is illegal value!");
     }
    }
    public Node(Slot slot) {
     this.slot = slot;
    }
    public Node(int s) {
     this(Slot.fromInt(s));
     switch(slot) {
      case empty: /* special stuff for empty */ break;
      case start: /* special stuff for start */ break;
      /* ... */
     }
    }
    private Slot slot;
}
Ray Tayek
+2  A: 

An alternate approach to the other suggestions is that you can enter the values in the enum constructors rather than having to loop over afterwards:

public class MazeNode {
    private static final Map<Integer, SlotValue> mapping = new HashMap<Integer, SlotValue>();

    enum SlotValue {
     empty(0),start(1),wall(2),visited(3),end(9);

     private final int m_representation;

     SlotValue(int representation) {
      m_representation = representation;
      mapping.put(Integer.valueOf(representation), this);
     }

    }

    SlotValue getSlotValue(int representation) {
     return mapping.get(Integer.valueOf(representation));
    }

}
Tom
I do like this simple because it has better performance, but then again I suppose a loop of 5 items (there will never be any more) isn't all that expensive either. :)
byte