I accidentally discovered something interesting about constant fields in Java, so I decided to share it here in a short article.
When we need to create a constant field in Java, we somehow automatically use this conventional construction:
Why does it take so many keywords to create a constant field?
Let’s take a look!
static final
As we know, static
makes the value belong to a class rather than a particular instance,
and final
means that we are no longer allowed to assign a different value to that field.
It makes sense for a constant field to be final
, so we can’t change its value.
It also logically makes sense for it to be static
, because any new instance would have
the same value anyway, so why not have it belong to the class?
But technically, is it mandatory to add static
?
ConstantValue
attribute
Let’s consider the following example:
In the bytecode, we see the following:
Here we see that the final fields have a ConstantValue
attribute. According to the JVM Specification
chapter about the class file format, this attribute is meant to store the value of a constant expression.
When the ACC_STATIC
flag is set, the field is assigned the value represented by the ConstantValue
attribute.
Otherwise, the JVM silently ignores the attribute.
The specifications do not explicitly mention anything about ACC_FINAL
.
We could, perhaps, derive its necessity from the mysterious “constant expression” formulation.
initialization
If we look further into the bytecode generated by the javac
compiler, we will find a static
block where STATIC_TEST
is initialized. This block is invoked at runtime when our class is referenced
for the first time:
We will also find that FINAL_TEST
is initialized inside of the constructor which is invoked at
runtime when we create a new instance of our class:
In the end, the only field that is not explicitly initialized in the bytecode is TEST
, which has both ACC_FINAL
and ACC_STATIC
flags. So where is that value actually initialized?
There are several steps that JVM performs in order to load our class. One of these steps is initialization,
and this is also the step when our static block is invoked. The TEST
field is special for the reason that
it has both ACC_STATIC
and ACC_FINAL
flags, which hints JVM that there must be some ConstantValue
attribute
and the field should be initialized with the value stored under that attribute. So JVM will do it internally
without the need to explicitly coding it in the bytecode.
extra fun experiment
javac
generates sane bytecode. The ConstantValue
attribute will be present only if the ACC_FINAL
flag is set.
However, what happens if another compiler or tool, following the specifications, generates bytecode with a
static non-final field that has a ConstantValue
attribute… And no initialization in the bytecode?
Will JVM initialize it in that case? I tested it with OpenJDK and OpenJ9, and strangely they did :) The Fernflower decompiler, on the other hand, doesn’t recognise that there is any initialization going on at all.
couple of conclusions
- Is the static modifier mandatory for the constant values? Technically, not. Logically, yes. It makes the bytecode more compact by utilizing the
ConstantValue
attribute. It also takes less memory at runtime. It simply makes more sense. - Is the final modifier mandatory? Absolutely. It helps us control our code. Compared to it,
ConstantValue
attribute is not that important, and it’s here solely for initialization purposes. So if we miss astatic
keyword, we are somewhat fine.final
, on the other hand, helps us enforce constant value in the Java code. - Are the JVM specs fully clear? Let me show you a quote from the section that describes the class initialization step:
… initialize each final static field of C with the constant value in its
ConstantValue
attribute (§4.7.2), in the order the fields appear in the ClassFile structure.
While this quote explicitly mentions final
, the section related to class file format and ConstantValue
attribute in particular doesn’t say a word about it. Following this specification, most of the JVM
implementations don’t consider final as mandatory in order to use ConstantValue
… So I’ll leave the last
question for you to answer :)