tags:

views:

143

answers:

2

Does Java allow something like good ol' C or even C# in the sense that you can define an enum with fields that grow in value automatically, and start at an optionally given value?

E.g.

In C or C#:

enum Foo { A = 10, B, C, D = 5000, E, Fish };

Yields A = 10, B = 11, C = 12, D = 5000, E = 5001, Fish = 5002.

+5  A: 

In Java you can't specify the ordinal values explicitly at all. They always autoincrement, from 0, with no control over it.

If you want other custom values, you need to put them in constructor calls and store them yourself. You can get autoincrement, but it's icky as heck:

import java.util.EnumSet;

// Please don't ever use this code. It's here so you can point and laugh.
enum Foo 
{ 
    A(10), B, C, D(5000), E, Fish;

    private static int nextValue;
    private int value;

    private Foo()
    {
        this(Counter.nextValue);
    }

    private Foo(int value)
    {
        this.value = value;
        Counter.nextValue = value + 1;
    }

    public int getValue() 
    {
        return value;
    }

    private static class Counter
    {
        private static int nextValue = 0;
    }
}

public class Test
{
    public static void main(String[] args)
    {
        for (Foo foo : EnumSet.allOf(Foo.class))
        {
            System.out.println(foo.name() + " " + 
                               foo.ordinal() + " " + 
                               foo.getValue());
        }
    }
}

Note the need for the nested class, because you can't access static fields within an enum constructor. Ick, ick, ick. Please don't do this.

Jon Skeet
`It's here so you can point and laugh.` Haha. Good one!
Alex
the default constructor needs to update Counter.nextValue, and you can probably remove the nextValue directly inside Foo, too.
Yuliy
@Yuliy: The parameterless constructor calls the parameterised one, so that's updating Counter.nextValue. Putting nextValue directly inside Foo *doesn't* work - at least not with the version of javac I'm using. It prevents you from accessing static fields in the constructor.
Jon Skeet
+5  A: 

This is a design choice of Java Enums to not support to change the ordinal values. Basically, they are not stable enough to depend on them. If you change the position of B and C in your example clients depending on the ordinal values are broken. This may happen unintentionally.

The problem is described in Effective Java Item 31: Use instance field instead of ordinals.

You can emulate the behavior in a stable manner:

enum X{
    A(10), B(A), C(B), D(5000), E(D), F(E);

    private final int value;

    X(int value){
        this.value = value;
    }

    X(X preceding){
        this.value = preceding.getValue() + 1;
    }

    public int getValue() {
        return value;
    }

    @Override
    public String toString() {
        return this.name() + "(" + this.value + ")";
    }

    static {
        Set<Integer> values = new HashSet<Integer>();
        for(X x : X.values()) values.add(x.value);
        assert(values.size() == X.values().length); //no duplicates
    }
}

With this definition you may change the order of the values without breaking clients.

Calling for(X x : X.values()) System.out.println(x); returns:

A(10)
B(11)
C(12)
D(5000)
E(5001)
F(5002)
Thomas Jung
Kinda sucks though -- they have their uses. Such as when I don't want my enums having values below N, for example (maybe I want to reserve that range to be off-limits). But I also don't want to manually define each integer value myself...
Alex
@alex: that means you should have a field, which computes the number from the ordinal, given the constraints of the range you want.
Chii
@Alex - The emulated version should work for you. It's stable and you do not have to put every int value in explicitly.
Thomas Jung
I like the use of the preceding value. Neat.
Jon Skeet
Heh, I think I'll just resort to final static int's and doing it manually. Specifying `B(A), C(B), ...,` doesn't save me much :). Thanks, though.
Alex