Maybe we should first reconsider the term “variable”. In most contexts, the term “variable” includes local variables, static
and non-static
fields and more than often even array elements (e.g., in the memory model). Since array elements do not support being final
, the answer can only be given for fields and local variables.
For fields, there is the ACC_FINAL
flag, which tells whether the field is final
or not. It has different consequences. static final
fields can only be written in the class initializer whereas final
instance fields are not only writable in the constructor but also via Reflection with access override. A JVM trying to take a benefit from the final
nature of an instance field when optimizing, has to take care to detect reflective modifications.
For local variables, there is no final
flag, in fact, there is not even a formal declaration at all. In the Java byte code, local variables are only indices within the stack frame, reused at will without premonition. So a write to a local variable index could be either, a change of the variable or a reuse of the same index for a new variable, e.g. { int x=4; } { int y=5; }
may get compiled to the same byte code as { int x=4; x=5; }
.
For the JVM’s optimizer, it doesn’t matter anyway, as it will transform the operations on the local variables into SSA form, so, with the example above, the optimizer will treat the code as having to constants, c₁:=4
and c₂:=5
, and depending on the position of the subsequent code it can be determined which constant is uses, in other words, it’s more than having “effectively final” variables, even changing variables can be treated like multiple final
variables (in the absence of thread synchronization, even changes to heap variables may temporarily get a similar treatment).