12

How do I make a generic enum rotator? It would be a generic version of next() in this example.

    public class TestEnum
    {
        enum Temperature { hot, cold };

        public static void main(String[] args)
        {
            Temperature t = Temperature.hot;
            Temperature t2 = next(t);
        }

        static Temperature next(Temperature t)
        {
            if (t.ordinal() == Temperature.values().length - 1)
                return Temperature.values()[0];
            else
                return Temperature.values()[t.ordinal() + 1];
        }
    }

Edit: In the comments, @irreputable suggests a superior solution. irreputable if you post it as an answer I will select it. You might want to save time by copying this answer.

static <T extends Enum<T>> T next(T t)
{
    int index = t.ordinal();
    @SuppressWarnings("unchecked")
    Enum<T>[] constants = t.getClass().getEnumConstants();
    if (index == constants.length - 1)
    {
        @SuppressWarnings("unchecked")
        T result = (T)constants[0];
        return result;
    }
    else
    {
        @SuppressWarnings("unchecked")
        T result = (T)constants[index + 1];
        return result;
    }
}
H2ONaCl
  • 10,644
  • 14
  • 70
  • 114

6 Answers6

10

Try:

public class Test {

    enum Temperature { hot, cold };

        public static void main(String[] args) throws Exception
        {
            Temperature t = Temperature.hot;
            Temperature t2 = next(t);
            System.out.println(t2);
        }

        static <T extends Enum> T next(T t) throws Exception
        {
            Method values = t.getClass().getMethod("values");
            if (t.ordinal() == ((T[])values.invoke(t)).length - 1)
                return ((T[])values.invoke(t))[0];
            else
                return ((T[])values.invoke(t))[t.ordinal() + 1];
        }
}
Ernesto Campohermoso
  • 7,213
  • 1
  • 40
  • 51
  • As a general purpose rotator, I think this is a good answer. I am going to use this, plus `@SuppressWarnings("unchecked")`, `>` to define the type, and explicitly catch `NoSuchMethodException, InvocationTargetException, IllegalAccessException` and I will throw `RuntimeException` because I think enum rotation is low risk. – H2ONaCl Dec 10 '11 at 17:52
  • use `Class.getEnumConstants()` – irreputable Dec 10 '11 at 19:01
5

IMO, relying on position to think of "next" or "previous" would be a bad idea, just as it is a bad idea to rely on ordinal() in your application code. It would make more sense to have your enum determine how the rotation should take place so that adding new members in between wouldn't screw up stuff.

There are two solutions you can try out: making sure each enum created implicitly handles the next intelligently which provides for greater degree of flexibility or create a generic rotation policy which can be used by all enums which supports rotation but has less flexibility.

Approach 1 (greater flexibility)

enum Direction1 {

    EAST() {
        @Override
        public Direction1 next(Rotation rotation) {
            return rotation == Rotation.CLOCKWISE ? SOUTH : NORTH;
        }
    },
    WEST() {
        @Override
        public Direction1 next(Rotation rotation) {
            return rotation == Rotation.CLOCKWISE ? NORTH : SOUTH;
        }
    },
    SOUTH() {
        @Override
        public Direction1 next(Rotation rotation) {
            return rotation == Rotation.CLOCKWISE ? WEST : EAST;
        }
    },
    NORTH() {
        @Override
        public Direction1 next(Rotation rotation) {
            return rotation == Rotation.CLOCKWISE ? EAST : WEST;
        }
    };

    abstract Direction1 next(Rotation rotation);

    static enum Rotation {
        CLOCKWISE, ANTICLOCKWISE
    };

}

Approach 2, more generic, less flexibility

interface Rotatable {
    // now it would be difficult to make next() method take a rotation
    Rotatable next();
}

enum Direction2 implements Rotatable {

    // assume clockwise rotation

    EAST() {
        @Override
        public Direction2 next() {
            return SOUTH;
        }
    },
    WEST() {
        @Override
        public Direction2 next() {
            return NORTH;
        }
    },
    SOUTH() {
        @Override
        public Direction2 next() {
            return WEST;
        }
    },
    NORTH() {
        @Override
        public Direction2 next() {
            return EAST;
        }
    };

}
Sanjay T. Sharma
  • 22,857
  • 4
  • 59
  • 71
0

I believe using ordinal values is always a fragile approach as it will rely on position, either use values() or class.getEnumConstants().

Littm
  • 4,923
  • 4
  • 30
  • 38
Ashish
  • 11
  • 3
0
public <T extends Enum<T>> T rotate(T current, int increment) {
  T[] values = current.getDeclaringClass().getEnumConstants();
  int i = current.ordinal() + increment;
  while (i<0) i+= values.length; 
  return values[i%values.length];
}

Temperature next = rotate(hot, 1);
Temperature prev = rotate(hot, -1);
Temperature nextNext = rotate(hot, 2);
ddimitrov
  • 3,293
  • 3
  • 31
  • 46
0

Using an interface:

public interface EnumInterface {
    public EnumInterface[] values1();
    public int ordinal1();
}   

enum Temperature implements EnumInterface {
    HOT,
    COLD;

    public EnumInterface[] values1() {
        return values();
    }

    public int ordinal1() {
        return ordinal();
    }
}

static <T extends EnumInterface> T next(T t) {      
    if (t.ordinal1() == t.values1().length - 1)
        return (T) t.values1()[0];
    else
        return (T) t.values1()[t.ordinal1() + 1];
} 

public static void main(String[] args) {
    Temperature t = Temperature.HOT;
    Temperature t2 = next(t);
    System.out.println(t2);
}
Tudor
  • 61,523
  • 12
  • 102
  • 142
0

This is not a direct answer to a question but rather a different approach to what you are trying to do.

public interface<T extends Enum<T>> NextEnum {
    T next();
}

public enum Temperature implements NextEnum<Temperature> {
    hot, cold;

    public Temperature next() {
        if (ordinal() == Temperature.values().length - 1)
            return Temperature.values()[0];
        else
            return Temperature.values()[ordinal() + 1];
    }
}

For all enums that implement NextEnum you can use the next() method. For example:

Temperature.hot.next();
Marcelo
  • 11,218
  • 1
  • 37
  • 51