Why Can't Enum's Constructor Access Static Fields

Why can't enum's constructor access static fields?

The constructor is called before the static fields have all been initialized, because the static fields (including those representing the enum values) are initialized in textual order, and the enum values always come before the other fields. Note that in your class example you haven't shown where ABBREV_MAP is initialized - if it's after SUNDAY, you'll get an exception when the class is initialized.

Yes, it's a bit of a pain and could probably have been designed better.

However, the usual answer in my experience is to have a static {} block at the end of all the static initializers, and do all static initialization there, using EnumSet.allOf to get at all the values.

Why couldn't I access the static field in enum in constructor?

You can use this "trick":

private static class IntHolder {
static int negativeOffset;
}

then refer to the variable like this:

IntHolder.negativeOffset ++;

and

return IntHolder.negativeOffset; 

The reason this works is that the JVM guarantees that the variable is initialized when the IntHolder static inner class is initialized, which doesn't happen until it's accessed.

The whole class will then be as follows, which compiles:

enum Attribute { POSITIVE, NEGATIVE }
enum Content {
C1(Attribute.POSITIVE),
C2(Attribute.POSITIVE),
... // some other positive enum instances.
Cm(Attribute.NEGATIVE),
... // some other negative enum instances.
Cn(Attribute.NEGATIVE);

private final Attribute a;

private static class IntHolder {
static int negativeOffset;
}

private Content(Attribute a) {
this.a = a;
if ( a == Attribute.POSITIVE) {
IntHolder.negativeOffset ++;
}
}

public static int getNegativeOffset() { return IntHolder.negativeOffset; }
}

Note the corrections for spelling errors and the simpler comparison with the Attribute enum value using == instead of compareTo()

Enum and static variable in constructor

The compiler allows a call to a static function, because it is not smart enough to prohibit it: the legitimacy of the call cannot be deduced without looking into the body of the incrementCount method.

The reason this is prohibited becomes clear when you run the following code:

enum TrickyEnum
{
TrickyEnum1, TrickyEnum2;

static int count = 123; // Added an initial value

TrickyEnum()
{
incrementCount();
}

private static void incrementCount()
{
count++;
System.out.println("Count: " + count);
}

public static void showCount()
{
System.out.println("Count: " + count);
}
}

public static void main (String[] args) throws java.lang.Exception
{
TrickyEnum te = TrickyEnum.TrickyEnum1;
TrickyEnum.showCount();
}

This prints

1
2
123

which is extremely confusing to a programmer reading your code: essentially, incrementCount does its modifications to the static field before it has been initialized.

Here is a demo of this code on ideone.

Private enums and static fields in the enclosing class

This is a bit all over the place in the JLS. The When Initialization Occurs chapter states

The intent is that a class or interface type has a set of initializers
that put it in a consistent state, and that this state is the first
state that is observed by other classes. The static initializers and
class variable initializers are executed in textual order, and may not
refer to class variables declared in the class whose declarations
appear textually after the use, even though these class variables are
in scope (§8.3.3). This restriction is designed to detect, at compile
time, most circular or otherwise malformed initializations.

That bold snippet refers to the class directly containing the access.

enum types are defined in the Java Language Specification, here

An enum declaration specifies a new enum type, a special kind of class type.

The fields you access in the Baz constructor

Baz(String description) {
// Can access static fields from before the enum
first.add(description);

// Can access static fields from _after_ the enum
second.add(description);
}

are not class variables declared in the class, the enum type Baz. The access is therefore allowed.

We can go even deeper into the detailed class initialization procedure, which explains that each class (class, interface, enum) is initialized independently. However, we can create an example that sees yet-to-be-initialized values

public class Example {
public static void main(String[] args) throws Exception {
new Bar();
}
}

class Bar {
static Foo foo = Foo.A;
static Integer max = 42;

enum Foo {
A;

Foo() {
System.out.println(max);
}
}
}

This will print null. The access to max is allowed in the enum type's constructor although our program execution reached the access before max was initialized. The JLS warns against this

The fact that initialization code is unrestricted allows examples to
be constructed where the value of a class variable can be observed
when it still has its initial default value, before its initializing
expression is evaluated, but such examples are rare in practice. (Such
examples can be also constructed for instance variable initialization
(§12.5).) The full power of the Java programming language is available
in these initializers; programmers must exercise some care.


Your original Foo example introduces an extra rule, defined in the chapter on Enum Body Declarations .

It is a compile-time error to reference a static field of an enum type
from constructors, instance initializers, or instance variable
initializer expressions of the enum type, unless the field is a
constant variable (§4.12.4).

That rule blocks your Foo snippet from compiling.

enum constants translate to public static final fields. These appear textually first in the enum type definition and are therefore initialized first. Their initialization involves the constructor. The rules exists to prevent the constructor from seeing uninitialized values of other class variables that will necessarily be initialized later.

Why can't the instance member be accessed from the lambda of the enum constructor?

The lambda expression is not a member of the enum, so it cannot access member variables from the enum directly. It also has no access to protected and private members of the enum. Also, at the point where the lambda is passed to the constructor, the member variables of the enum are not in scope.

A possible solution is to pass symbol as a third parameter to the lambda expression, but that means you'll have to use a different functional interface than DoubleBinaryOperator.

For example:

interface CalculationOperation {
double calculate(double x, double y, String symbol);
}

public enum Operation {
PLUS("+", (x, y, symbol) -> {
System.out.println(symbol);
return x + y;
}),
MINUS("-", (x, y, symbol) -> x - y),
TIMES("*", (x, y, symbol) -> x * y),
DIVIDE("/", (x, y, symbol) -> x / y);

Operation(String symbol, CalculationOperation op) {
this.symbol = symbol;
this.op = op;
}

public String getSymbol() {
return symbol;
}

protected final String symbol;
private final CalculationOperation op;

public double apply(double x, double y) {
return op.calculate(x, y, symbol);
}
}

Can an enum have a constructors for each of its constants

As for first question: you cannot have separate constructors, but you can work-around this in the following manner:

public enum EnumTest {
ONE() {
void init() {
val = 2;
}
},
TWO() {
void init() {
val = 1;
}
};

protected int val;

abstract void init();

EnumTest() {
init();
}
}

This way you technically have separate initialization methods for different constants.

Another way is to use initializer sections:

public enum EnumTest {
ONE() {{
val = 2;
}},
TWO() {{
val = 1;
}};

protected int val;
}

As for your second question: constant fields are not accessible during enum construction, because enum constants are accessible for static fields. For example, this code compiles correctly:

public enum EnumTest {
ONE, TWO;

public static final String ONE_STRING = ONE.toString();
}

If accessing ONE_STRING from the constructor were allowed, you would either had an endless initialization loop or accessing of not-yet-initialized enum constant.

Execution order of of static blocks in an Enum type w.r.t to constructor

I understand your question as: why is there a guarantee that the enum constants will be initialised before the static block is run. The answer is given in the JLS, and a specific example is given in #8.9.2.1, with the following explanation:

static initialization occurs top to bottom.

and the enums constants are implicitly final static and are declared before the static initializer block.

EDIT

The behaviour is not different from a normal class. The code below prints:

In constructor: PLUS
PLUS == null MINUS == null

In constructor: MINUS
PLUS != null MINUS == null

In static initialiser
PLUS != null MINUS != null

In constructor: after static
PLUS != null MINUS != null
public class Operation {

private final static Operation PLUS = new Operation("PLUS");
private final static Operation MINUS = new Operation("MINUS");

static {
System.out.println("In static initialiser");
System.out.print("PLUS = " + PLUS);
System.out.println("\tMINUS = " + MINUS);
}

public Operation(String s) {
System.out.println("In constructor: " + s);
System.out.print("PLUS = " + PLUS);
System.out.println("\tMINUS = " + MINUS);
}

public static void main(String[] args) {
Operation afterStatic = new Operation ("after static");
}
}

Is there a workaround for not being able to access static methods in an enum inside its constructor?

Add a static initializer to your class:

public enum Number {

ONE(1),
TWO(2),
THREE(3);

static {
Set<Integer> seen = new HashSet<>();
for (Number n : values()) {
if (!seen.add(n.id)) {
throw new IllegalArgumentException(...);
}
}
}

private final int id;

private Number(int id) { this.id = id }
}


Related Topics



Leave a reply



Submit