15

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)

Janick Bernet
  • 20,544
  • 2
  • 29
  • 55
  • You may want to read [this](http://stackoverflow.com/questions/32959680/invoking-a-method-of-an-anonymous-class/32959751#32959751). – Sotirios Delimanolis Dec 18 '15 at 04:21
  • @SotiriosDelimanolis: Yes I see your point, so we generally cannot have members of anonymous classes as a programmers and I guess that extends to the compiler that there cannot be any anonymous class fields etc. (Which is essentially what @Pshemo wrote, too) But then if that was the point where it breaks down, make it a named class `Test.Value1` and it should work again. – Janick Bernet Dec 18 '15 at 04:29

3 Answers3

7

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()).

Raul Santelices
  • 1,030
  • 11
  • 17
  • Yes, which is all true but still doesn't answer my question *why* the compiler sees `VALUE1` as of type `Test` instead of of type `Test$1` that it clearly knows it actually is (see output of code snipped I linked). – Janick Bernet Dec 18 '15 at 03:34
  • Unfortunately, for the compiler, anonymous classes are not named -- which is why they are called anonymous. They are intended only to customize behavior without adding another static type. It's true that they end up getting a name using that convention with suffix `$1`, `$2`, etc., but only for JVM use. The main point is that the type of a reference is always defined "on its left": `Test VALUE1 = ...`. The type on the right (the anonymous class) in this case is a subtype of `Test` but not the type of the reference `VALUE1`. – Raul Santelices Dec 18 '15 at 03:39
  • Also, regarding your suggestion with `m()`: The whole point is that I want to have different fields per enum value, so adding methods in the base class would completely defeat that. As I wrote, I don't see any reason except simplicity/strictness (i.e. at compile time we should only see `Test` and not the concrete type) for this that any technical / type theoretical restriction. – Janick Bernet Dec 18 '15 at 03:40
  • Yes, anonymous classes are not named, but here we clearly have compile time constant `Test.VALUE1` that will always have an instance of type `Test$1`. The compiler does know all that. Now I think the reason actually why it doesn't work is because the way it implements the members, as they are always of type `Test` and not of their concrete types (which they could be). – Janick Bernet Dec 18 '15 at 03:45
  • It is frustrating in a sense. Other than having generic methods like `m()` to expose instance-specific values, using reflection is the only way I can quickly think of solving this problem. Perhaps a good library, or some other JVM language, can make accessing these fields easier. – Raul Santelices Dec 18 '15 at 03:46
  • Yes, I'm actually also not really looking for a workaround, I was rather more looking for reasons why there might be a technical/type theoretical reason for this limitation :) – Janick Bernet Dec 18 '15 at 03:49
  • @JanickBernet As for your third comment, you nail it. `VALUE1` is just a reference. The instance of the anonymous class defined on the right hand side is the actual object which `VALUE1` references, but it is not `VALUE1` itself (it's the usual distinction between reference and object). – Raul Santelices Dec 18 '15 at 03:49
  • This all goes back to Java's in ability to fully handle generic types. Once that problem is solved, I think this one will follow suite – smac89 Dec 18 '15 at 04:01
  • @Smac89: I don't think it has anything to do with generics. I added a lot of code to my question, and the crucial part seems to be the way the basic `Test` enum is generated: By throwing away all the concrete type information. However, I don't see a reason for that. The values array `Test[] Test.$VALUES` is perfectly capable of containing any subclass of `Test`, whereas accessing the values directly through their name `Test.VALUE1` we would retain the concrete type. Seems win-win to me! – Janick Bernet Dec 18 '15 at 04:14
3

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.

Sotirios Delimanolis
  • 274,122
  • 60
  • 696
  • 724
  • As per my previous comment, I think I wrote all this already in my question, maybe not clearly enough: It still is not clear to me why the compiler doesn't make use of the knowledge that it actually knows at compile that, in your example, we always have an instance of Child. – Janick Bernet Dec 18 '15 at 03:48
  • 1
    @JanickBernet It's more obvious in the `Child` example I gave. The compiler doesn't know at runtime what the `ref` variable will contain. Whether it's a reference to a `Child` object, or a reference to a `SomeOtherSubclass`. Enum types are just special reference types. The compiler doesn't have different rules for method or field access expressions for them. – Sotirios Delimanolis Dec 18 '15 at 03:51
2
VALUE1() {
  public static final String CONST_RELATED_TO_VALUE1 = "constant";
  public static final String OTHER_CONST_RELATED_TO_VALUE1 = "constant";
}

is an anonymous class which extends Test enum. In case of such classes we can access its members (without help of reflection) only when we do it directly after its creation like:

class Foo{
    public static void main(String[] args) {
        System.out.println(new Foo(){
            public String x = "foo";
        }.x);
    }
}

But if we write something like:

Foo f = new Foo(){
    public String x = "foo";
};
System.out.println(f.x);

we will get compilation error since f is of type Foo which doesn't have x member declared.
And that is the problem with your enum. What you did here:

VALUE1() {
  public static final String CONST_RELATED_TO_VALUE1 = "constant";
  public static final String OTHER_CONST_RELATED_TO_VALUE1 = "constant";
}

is in fact:

public static final Test VALUE1 = new Test(){
//                  ^^^^^^^^^^^
  public static final String CONST_RELATED_TO_VALUE1 = "constant";
  public static final String OTHER_CONST_RELATED_TO_VALUE1 = "constant";
}

so as you see VALUE1 (and other enum constants) are of type Test, not Test$1 (name of anonymous class given by compiler).

Why Test type was chosen over Test$1? Well, it is probably because variables can't be declared with anonymous type (we can't have Test$1 foo variable) and all enum types are actually compiled into simple classes which extends Enum class so same rules must apply for its fields (constants).

Pshemo
  • 122,468
  • 25
  • 185
  • 269
  • That is not a problem, I can always access a static member through an object instance. Regarding the interface, as I said, I'm clear that that's the way it's currently implemented, but I don't see any reason it has to be that way, the compiler knows the exact type at that point. – Janick Bernet Dec 18 '15 at 03:24
  • @JanickBernet I rewritten my answer. – Pshemo Dec 18 '15 at 03:52
  • Thanks, that was much more in line with what was looking for. That said, I'm not sure about "variables can't be declared with anonymous type": I'm pretty sure the compiler does not have that limitation, but it might be more involved. If the way the compiler works it transforms enum into the code you've written and then in a separate step it does the anonymous class generation, then it makes sense it does it like this. But I would have guessed it does do all the enum creation at once. Do you know this for a fact? Definitely the best explanation so far though. – Janick Bernet Dec 18 '15 at 04:20
  • Compiler is limited to Java Language Specification and I don't remember JLS allowing us to create such variables, which probably also prevents compiler from creating them. For now it is wild guess of a man who is almost sleeping so I may be wrong, but that is my last *Java* though for today (probably). – Pshemo Dec 18 '15 at 04:26
  • Actually, if the issue is anonymous classes, simply creating named inner classes `Test$Value1` and `Test$Value2` would fix that with the added benefit of actually being clearer as to map to the corresponding value. – Janick Bernet Dec 18 '15 at 04:32