tags:

views:

70

answers:

2

I'm attempting to translate some C++ code into Java. I'm looking for the best way to emulate this type of C++ paradigm in Java -- I think enums are probably the answer but I'm open to anything

C++ code:

typedef UInt32 Type;
enum { UNKNOWN, QUIT, SYSTEM, TIMER, LAST }
...
Type newType = UNKNOWN;
Type nextType = LAST + 1;
...
// "Register" the new type
newType = nextType;
nextType++;
...
switch (newType) {
   case UNKNOWN:
   case QUIT:
   ...
   case LAST:
          // Ignore unset or predefined types
   default:
          // Some type other than the predefined.  Do something special

Essentially I'm looking for a way to "expand" the values of a Java enumeration.

enum Type { UNKNOWN, QUIT, SYSTEM, TIMER, LAST } 

doesn't quit cut it.

I like the idea of making a new object for strong typing.

Again, I'm looking for the best pattern to use here. I could easily float by with a few public static final int UNKNOWN, etc

+2  A: 

One advantage of Java enums is, that they are essentially objects like all others. In particular, you can have methods on the constants, and make your enum implement interfaces:

interface Useful {
    void doStuff();
}

enum Foo implements Useful {
    DEFAULT { 
        public void doStuff() {
            ...
        }
    },
    MAGIC {
        public void doStuff() { 
            ...
        }
    }
}

So, instead of taking arguments of the enum type, your methods could accept any implementation of the interface, and in particular, provide the default stuff by having the enum constants implement whatever is necessary.

They can also have members:

enum Explicit {
    FIRST(0), SECOND(1), THIRD(2);

    private final int value;

    private Explicit(int v) {
        this.value = v;
    }
}

Note, that constants have an internal numeric value (reflecting the position of the constant among its peers) which is accessible using the ordinal method:

assert Explicit.FIRST.ordinal() == 0;

But relying on this is a little bit dangerous. If you are going to (say) persist enum constants in a database, then the following change would break the code:

enum Explicit {
    FIRST(0), NEWELT(-1), SECOND(1), THIRD(2);

    private final int value;

    private Explicit(int v) {
        this.value = v;
    }
}

when using ordinal values. For this reason, the serialization machinery uses the constant's name instead of its position when serializing enum values.

So:

Type.X + 1

would be

Enum.values()[Enum.X.ordinal() + 1]

and the switch could be modelled using interfaces implemented by the enum itself (you can use enums in switch statements in Java, but often, making the enum implement the necessary code yields more maintainable code)

Dirk
Yeah that's pretty much exactly what I had. Unfortunately enum.values()[LAST.ordinal() + 1] will be out of bounds. I need something like an enum with "expanding" values.
Shaun
@Shaun. See edit. Use an interface instead of the enum value, and allow (client code? whatsoever) to provide extensions by providing new implementations of that interface. The enums are still handy for implementing a limited range of default behaviour.
Dirk
+1  A: 

If each value of Type is just an int, then I'd probably go for a bunch of static final ints in a class.

Just for the record, something like this works more or less type-safely. I can't determine if the complexity is warranted without knowing more about the problem.

public class abstract Type
{ 
   public static final Type UNKNOWN   = registerStd("UNKNOWN");
   ...
   public static final Type TIMER     = registerStd("TIMER");

   // use this to keep track of all the types
   private static final List<Type> REGISTERED = ...

   //This will do your switch statement for you, implemented by 
   // anonymous subclasses
   public abstract void dispatch(Type onMe);

   // here's how you make the standard ones
   private static Type registerStd(String name)
   {
       Type heresOne = new Type(name) 
       { 
          // note, it's a no-op
          public void dispatch(DoStuffer algorithm) {} 
       };
       REGISTERED.add(heresOne);
       return heresOne;
   }

   //here's how you make the non-standard ones
   public static Type registerNew(String name)
   {
       Type heresOne = new Type(name) 
       {
          public void dispatch(DoStuffer algorithm) {algorithm.execute(this)} 
       };
       REGISTERED.add(heresOne);
       return heresOne;
   }

}

public interface DoStuffer 
{
   public void execute(Type onMe);
}

//Then your code becomes
Type newType = Type.registerNew("NewType");

newType.dispatch
(
     new DoStuffer() 
     {
        public void algorithm(Type forMe) { ... } 
     }
);

Maybe this is a little esoteric. But it does allow for "easy dispatch" at the caller site and an extensible enum, in some sense.

Burleigh Bear