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. Thestatic
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
How to Create Sparksession with Hive Support (Fails with "Hive Classes Are Not Found")
Java Streams: Replacing Groupingby and Reducing by Tomap
Swing's Keylistener and Multiple Keys Pressed at the Same Time
Resizing Icon to Fit on Jbutton in Java
Is the Order Guaranteed for the Return of Keys and Values from a Linkedhashmap Object
Using Gzip Compression with Spring Boot/Mvc/Javaconfig with Restful
Hadoop No Filesystem for Scheme: File
Providing Input/Subcommands to Command Executed Over Ssh with Jsch
Jtable Model Listener Detects Inserted Rows Too Soon (Before They Are Drawn)
Selenium Switch Focus to Tab, Which Opened After Clicking Link
Cast Double to Integer in Java
Java Spring Boot: How to Map My App Root ("/") to Index.Html
Building Executable Jar with Maven
Should I Declare Jackson's Objectmapper as a Static Field
Java Inetaddress.Getlocalhost(); Returns 127.0.0.1 ... How to Get Real Ip