Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why can't I access static final members from a dedicated enum value in Java

I was wondering why, while it's perfectly valid to do the following in Java

public enum Test {
   VALUE1() {
      public static final String CONST_RELATED_TO_VALUE1 = "constant";
      public static final String OTHER_CONST_RELATED_TO_VALUE1 = "constant";
   },
   VALUE2() {
      public static final String CONST_RELATED_TO_VALUE2 = "constant";
   },
   VALUE3;
}

accessing the constants as one would expect using Test.VALUE1.CONST_RELATED_TO_VALUE1 does not work.

Now I understand, VALUE1, VALUE2 etc. are actually all generally seen as static final instance of type Test and hence don't have those fields, but the information should theoretically available at compile time, which can easily be verified running a little test

     // print types and static members
     for (Object o: Test.values()) {
        System.out.println(o.toString() + ": " + o.getClass());
        for (Field field : o.getClass().getDeclaredFields()) {
            if (java.lang.reflect.Modifier.isStatic(field.getModifiers())) {
                System.out.println("\t" + field);
            }
        }             
     }

which results in the following output

VALUE1: class Test$1                                                                                                                                                                               
        public static final java.lang.String Test$1.CONST_RELATED_TO_VALUE1                                                                                                                
        public static final java.lang.String Test$1.OTHER_CONST_RELATED_TO_VALUE1                                                                                                          
VALUE2: class Test$2                                                                                                                                                                               
        public static final java.lang.String Test$2.CONST_RELATED_TO_VALUE2                                                                                                                
VALUE3: class Test                                                                                                                                                                                 
        public static final Test Test.VALUE1                                                                                                                                    
        public static final Test Test.VALUE2                                                                                                                                    
        public static final Test Test.VALUE3                                                                                                                                    
        private static final Test[] Test.$VALUES

It's clear that we actually have proper dedicated sub-classes at runtime for VALUE1 and VALUE2. But it also looks like the reason we lose the concrete type information about VALUE1 and VALUE2 is the way the compiler generates the static enum values for the base enum class Test as used by VALUE3: All members are of type Test and the concrete types are discarded.

However, it seems to me that if the compiler simply kept those types like so

        public static final Test$1 Test.VALUE1                                                                                                                                    
        public static final Test$2 Test.VALUE2                                                                                                                                    
        public static final Test Test.VALUE3                                                                                                                                    

all surrounding code would still work. In addition we could also do what I tried initially and access CONST_RELATED_TO_VALUE1 through Test.VALUE1, which is now clearly an instance of type Test$1 and not just Test and while it should be generally avoided, it seems in this case perfectly fine to access that static member through an instance.

Now as many people correctly pointed out, using anonymous classes to the left is not valid Java code and probably also for the compiler not allowed without some major specification change. However, this could easily be solved by using named inner classes, so we would have

        public static final Test.Value1 Test.VALUE1                                                                                                                                    
        public static final Test.Value2 Test.VALUE2                                                                                                                                    
        public static final Test Test.VALUE3                                                                                                                                    

This even provides an added benefit for debugging that the inner sub class name clearly maps to the corresponding enum value.

Now I understand there would have to be some minor changes, but going from anonymous to named classes and not throwing away the types seems like a small change and this looks like quite a nice feature to have, without an easy way to emulate it using overridden members or something.

So I was wondering why this wasn't implemented like this except time? Am I missing something crucial here that would prevent the compiler from doing this (either regarding implementation complexity or type system impossibilities) was it just not implemented to keep it simpler, because there was no time or something along those lines?

(I'm mainly looking for reasons why it was decided to implement it like this from a compiler/typesystem point of view, not for practical alternatives to this, as there are definitely a couple, though it still seems like a nice pattern to have)

like image 291
Janick Bernet Avatar asked Dec 18 '15 03:12

Janick Bernet


People also ask

Is enum static final Java?

All constants defined in an enum are public static final . Since they are static , they can be accessed via EnumName.

Can enum have static fields?

But in case of Enums we cannot alter order of static fields. The first thing in enum must be the constants which are actually static final instances of enum type.

Should enum fields be final?

The members of the enum are constants, but the reference variable referring to enum values is just like any other reference variable, hence the need to mark them as final .

Are enum variables final?

There is a big difference: first, enum , is not a variable, it is a type. A final variable, say, an int , can have a single, preset value from among many possible values of int , while an enum variable (which could also be final ) can have a value from a set that you declare when defining the enum .


2 Answers

Static members such as CONST_RELATED_TO_VALUE1 are members of the anonymous class for the corresponding enum value, but not members of the enum class itself. As with other anonymous classes, the object VALUE1 here is declared as being of type Test even though it's an instance of the anonymous subclass of Test.

Therefore, you can't access CONST_RELATED_TO_VALUE1 via VALUE1.CONST_RELATED_TO_VALUE1 because VALUE1 is a reference of type Test and CONST_RELATED_TO_VALUE1 is a member of the anonymous subclass but not of Test. CONST_RELATED_TO_VALUE1 can only be accessed by other members of the anonymous class.

If you want to access values defined in the anonymous class for VALUE1, you need to have a method (say, m()) of the enum type that you override in the anonymous class for the enum object, and that returns or provides somehow the value that you want to expose (via VALUE1.m()).

like image 187
Raul Santelices Avatar answered Oct 07 '22 19:10

Raul Santelices


The enum constant are special variables that are of the type of their declaring enum type. In other words, the reference expression Test.VALUE1 is of type Test. The type TEST does not define a variable name CONST_RELATED_TO_VALUE1 and you therefore can't access one.

This is similar to doing

class Parent {
}

class Child extends Parent {
    public Object field = new Object();
}
...
Parent ref = new Child(); 
System.out.println(ref.field); // compilation error

except in your case you're trying to access a static field through a reference expression.


The optional bodies of enum constants define new anonymous classes that extend the enum type.

like image 42
Sotirios Delimanolis Avatar answered Oct 07 '22 20:10

Sotirios Delimanolis