10

I have an enum and I'm trying to pass a final static variable as parameter in constructor. Problem is that first statement in enums has to be a instance itself, but in that case, final variable is not defined yet. See the code and you will understand:

public enum ParseResource {

    PUSH(API_URL); //Error: illegal forward reference

    private final static String API_URL = "https://api.parse.com/1";

    private String url;
    private ParseResource(String url) {
        this.url = url;
    }
}

and the other option:

public enum ParseResource {

    //Illegal in enums, first statement has to be an instance
    private final static String API_URL = "https://api.parse.com/1";

    PUSH(API_URL);

    private ParseResource(String url) {
        this.url = url;
    }
}

How can I solve it? Thanks.

Héctor
  • 24,444
  • 35
  • 132
  • 243
  • 6
    The solution that comes to mind is: don't have your url in a static field in your enum. Put it outside the enum or in your constructor. – khelwood May 11 '15 at 13:39
  • Does this answer your question? [How to define static constants in a Java enum?](https://stackoverflow.com/questions/23608885/how-to-define-static-constants-in-a-java-enum) – 18446744073709551615 Mar 21 '23 at 12:00

3 Answers3

33

There are two possible solutions which seem reasonable to me.

  1. Using a nested class (which is initialized separately):

    public enum ParseResource {
        PUSH(Constants.API_URL);
    
        private static class Constants {
            private static final String API_URL = "https://api.parse.com/1";
        }
    
        private String url;
        private ParseResource(String url) { this.url = url; }
    }
    

    That's the most generally useful, because it doesn't impose any important restrictions.

  2. Using a method:

    public enum ParseResource {
        PUSH(getApiUrl());
    
        private static String getApiUrl() { return "https://api.parse.com/1"; }
    
        private String url;
        private ParseResource(String url) { this.url = url; }
    }
    

    One hidden downside to using a method is that a method invocation isn't a constant expression, so it can't be used for some things, like annotation element values.


There's also a 3rd possible way which works in practice, but as of Java 9 is no longer guaranteed by the JLS to work, so it shouldn't be used.

This used the qualified name ParseResource.API_URL instead of the simple name API_URL to circumvent the forward reference error, because API_URL is a constant variable (i.e. initialized with a String literal in this case):

public enum ParseResource {
    PUSH(ParseResource.API_URL);

    private static final String API_URL = "https://api.parse.com/1";

    private String url;
    private ParseResource(String url) { this.url = url; }
}

In Java 8, the good behavior of this was specified by 8.3.2:

Note that static fields that are constant variables are initialized before other static fields. [...] Such fields will never be observed to have their default initial values.

However, due to this bug report, the wording was changed to the following in Java 9:

Note that static fields that are constant variables are initialized before other static fields. [...] When such fields are referenced by simple name, they will never be observed to have their default initial values.

The above code was not affected by the defect described in the bug report, but as of Java 9 it's no longer guaranteed to work.

Radiodef
  • 37,180
  • 14
  • 90
  • 125
  • 1
    Or just declare the static final constant directly before instance fields, and access it as a fully qualified constant. It strangely works ;-) – ymajoros Jul 05 '16 at 09:48
  • Is it no longer guaranteed to compile or no longer guaranteed that code compiled using that technique in java 8 will run on a java 9 runtime? – Winter May 26 '19 at 16:52
  • @Winter In Java 9 and above, `PUSH.url` might be `null` in that example, because `API_URL` might be assigned after the enum constant initializations. – Radiodef May 27 '19 at 03:17
0

Enum are syntactic sugar that basically allow you to write nicer switch statements as well as iterate over a compile-time set of values of a certain class.

In this case you can define you compile-time string in a class like Constants - and the general rule when you face this kind of compile problems is that you should not use enum that way and need other classes

Raffaele
  • 20,627
  • 6
  • 47
  • 86
  • Yes, the presented example enum looks a bit odd, but I don't think your "general rule" exists. In a broader sense, regarding enums, there are a lot of more mechanics and use cases involved. Just take a closer look at https://docs.oracle.com/javase/10/docs/api/java/time/DayOfWeek.html. – Jens Piegsa May 01 '18 at 18:00
0

It is possible, you need to specify the class name:

class Scratch {
    public static void main(String[] args) {
        System.out.println(MyEnum.BAR3); // 10:BAR3
    }
}
enum MyEnum {
//  BAR1(       foo),   // error: illegal forward reference
//  BAR2(MyEnum.foo2),  // error: illegal forward reference
    BAR3(MyEnum.foo);   // no error

    int x;

    public static final int foo =10;
    public static       int foo2=20;
    MyEnum(int i) {x = i;}

    @Override public String toString() { return x+":"+super.toString(); }
}
18446744073709551615
  • 16,368
  • 4
  • 94
  • 127