1

Suppose, I want an int[] array to be shared by all instances of enum. Here is an example

public enum SampleEnum {
    Enum1(1), Enum2(2), Enum3(3), Enum4(4);

    private int[] values;

    private static final int[] SharedValues = {1, 2, 3, 4, 5};
    private static final int ValueCount = SharedValues.length;

    private SampleEnum(int factor) {
        // I prefer to calculate data once within constructor
        values = new int[ValueCount];
        for (int i=0; i<ValueCount; i++)
            values[i] = SharedValues[i] * factor;
    }

    private int[] getValues() {
       return values;
    }    
}

Guess what: I get message "Cannot refer to the static enum field within an initializer" for both ValueCount and SharedValues.

The problem can be overcome by placing the static array in a separate class:

class SampleEnumData {
    static final int[] SharedValues = {1, 2, 3, 4, 5};
}


public enum SampleEnum {
    Enum1(1), Enum2(2), Enum3(3), Enum4(4);

    private int[] values;

    private SampleEnum(int factor) {
        // I prefer to calculate data once within constructor
        int[] sharedValues = SampleEnumData.SharedValues;
        int valueCount = sharedValues.length;
        values = new int[valueCount];
        for (int i=0; i<valueCount; i++)
            values[i] = sharedValues[i] * factor;
    }

    private int[] getValues() {
       return values;
    }    
}

But this looks more as an awkward patch, than a logical solution.

It there a reason for not allowing a reference to a static class within enum initializer?

cyanide
  • 3,885
  • 3
  • 25
  • 33
  • The constructor is called before the static fields have all been initialized, see http://stackoverflow.com/questions/443980/why-cant-enums-constructor-access-static-fields/444000#444000 – BlackJoker Oct 21 '13 at 06:07
  • Thank you, J.Rush. As far as I know, this is the case for all classes, not only enum-s. The first instantiation of an element triggers static constructor and initializes all static fields. Why enum has a special restriction? – cyanide Oct 21 '13 at 06:13

2 Answers2

3

It there a reason for not allowing a reference to a static class within enum initializer?

It's a static field that you're not allowed access to, and there's a very good reason: the field will still have its initial value, because the enum members will be initialized before any later static field initializers. This is often a problem if you're trying to populate a map keyed by some aspect of the enum - you'd really like to perform the population in the constructor, but you can't because the map won't be created yet.

Options:

  • Perform all the values initialization in a static initializer block which is executed after all your instances have been constructed. The wrinkle here is that if you wish the field to be final, you'd need to create the array first, which means knowing how large to make it, without having access to the data. (Your choice of name is unfortunate here given the values() method which is also available here, mind you.)
  • Use a private nested class as a "holder" for the static field initialization. This is like your second solution, but without exposing the class.

The latter approach is probably the simplest, to be honest.

public enum SampleEnum {
    ...

    private static class SampleEnumData {
        static final int[] SHARED_VALUES = {1, 2, 3, 4, 5};
    }
}
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
0

Just want to provide another example. I was trying to create an offset/length dictionary of fields, that guarantees unique names and is resilient to someone moving fields, adding new ones in the middle, and so on.

I came across this "cannot access static field" when trying to reference offsets like this:

    enum Fields1 {
        FIELD1(4), FIELD2(1), FIELD3(8), FIELD4(8);        

        private static int nextOfs = 0;

        public final int size;
        public final int offset;

        private Fields1(int size) {
            this.size = size;
            this.offset = nextOfs;  // Cannot refer to the static enum field Fields1.nextOfs within an initializer
            nextOfs += size;        // Same here
        }
    }

After reading this post and its answer, I switched to this:

enum Fields {
    FIELD1(4), FIELD2(1), FIELD3(8), FIELD4(8);

    public final int size;
    public final int offset;

    private Fields(int size) {
        this.size = size;
        this.offset = OffsetAllocator.FieldsAllocator.getNextOfs(size);
    }

    private static final class OffsetAllocator {
        private static final OffsetAllocator FieldsAllocator = new OffsetAllocator();

        private int nextOfs = 0;

        private final int getNextOfs(int size) {
            int ofs = nextOfs;
            nextOfs += size;
            return ofs;
        }
    }
}
Alexandros
  • 2,097
  • 20
  • 27
  • Doesn't look like an efficient solution to me, given that each time the offset will be re-calculated (as if nothing has happened before!). A more logical way (suggested by my post) would be having an nextOfs as a static field in a different class (say OffsetClass), so you can keep the original code, only replacing nextOfs with OffsetClass.nextOfs. – cyanide Oct 09 '16 at 07:17
  • I've lost you at the very beginning on the re-calculation... The offset/size members are final and initialised once. Accessing FIELD1.offset several times will not recalculate anything... Or are you talking about something else? – Alexandros Oct 10 '16 at 00:14