Consider the following code:
public class Example {
static { System.out.println(Example.value); }
static final int value = 7;
public static void main(final String[] args) { }
}
I expected it not to compile, since value is used before it's declared. Why does this work?
I then tried with this code, and this doesn't compile as I expected
public class Example {
static{ System.out.println(value); }
static final int value = 7;
public static void main(final String[] args) { }
}
This version does not compile, and gives an Illegal forward reference error — which is what I originally expected.
The question is: Why are these two cases treated differently?
I have noticed another problem I would love to understand. If I use the first code and use a Integer.parseInt("7"); it compiles (I don't know why but I think for the same reason of the first code, so because i used the Example.value and not value) but this time doesn't print 7 but 0, surely I can understand that runtime is treated differently but that 0 is surely a default value i think, but final variable can't have default value. So why it compiles, and it also prints 0 (default value)?
public class Example {
static{ System.out.println(Example.value); }
static final int value = Integer.parseInt("7");
public static void main(final String[] args) { }
}
I expected it not to compile, since value is used before it's declared. Why does this work?
Members of a class are in scope everywhere within the body of the class, including lexically preceding their declarations. You have very likely relied on this yourself by, for example, having a constructor or method invoke a method declared later in the class. That a reference to a class variable appears before its declaration is not, by itself, a reason to reject it.
I then tried with this code, and this doesn't compile as I expected
The difference between your first code and your second is that the first refers to the class variable by its qualified name, whereas the second refers to it by its simple name. Scope notwithstanding, Java explicitly restricts accesses by simple name:
For a reference by simple name to a class variable f declared in class or interface C, it is a compile-time error if:
- The reference appears either in a class variable initializer of C or in a static initializer of C (§8.7); and
- The reference appears either in the initializer of f's own declarator or at a point to the left of f's declarator; and
- The reference is not on the left hand side of an assignment expression (§15.26); and
- The innermost class or interface enclosing the reference is C.
(JLS25 8.3.3; emphasis in the original)
That applies to your second code, but there is no such restriction on access by qualified name, such as your first code performs.
If I use the first code and use a
Integer.parseInt("7");it compiles [...] but this time doesn't print 7 but 0, surely I can understand that runtime is treated differently but that 0 is surely a default value i think, but final variable can't have default value.
Final variables can and do have default values:
Each class variable, instance variable, or array component is initialized with a default value when it is created (§15.9, §15.10.2)
(JLS25 4.12.5)
Class variables are created as part of preparation of their class (JLS25 12.3.2), which is a separate step before class initialization. For all intents and purposes, they come into existence with the default value appropriate for their data type. This is not considered an assignment, so it is not inconsistent with final fields later being assigned a value (once).
So why it compiles,
It compiles because it is valid, same as the first code.
and it also prints 0 (default value)?
It prints 0 because in this case, that's the value of the field at the time the static initializer is executed (as opposed to that value being 7 when the static initializer in the first code is executed). The difference follows from the fact that in the first code, Example.value is a constant variable, as that term is defined by Java:
A constant variable is a
finalvariable of primitive type or typeStringthat is initialized with a constant expression (§15.29).
As you can see, the same is not true of Example.value in your third code. This makes a difference for order of initialization, which is described in detail in JLS25, section 12.4.2. Static fields that are constant variables receive their final values at step 6 of the initialization procedure, but it's not until step 9 where the VM
execute[s ...] the class variable initializers and static initializers of the class, [...] in textual order, as though they were a single block.
It follows that no executable code, not even a static initializer or the initializer of a static variable, can observe the default value of a constant variable. However, note well "in textual order", and remember that class variables receive default values before the initialization procedure starts. Under the right circumstances, then, a static initializer or the initializer of a static variable, if suitably positioned, can and does observe the default values of final class variables that are not constant variables.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With