Here is the code:
public class Main {
public static void main(String[] args) {
new B();
}
}
class A {
A() {
System.out.println("A constructor before");
action();
System.out.println("A constructor after");
}
protected void action() {
System.out.println("Never called");
}
}
class B extends A {
private final int finalField = 42;
private int field = 99;
B() {
System.out.println("B constructor");
action();
}
public void action() {
System.out.println("B action, finalField=" + finalField + ", field=" + field);
}
}
And the result is:
A constructor before
B action, finalField=42, field=0
A constructor after
B constructor
B action, finalField=42, field=99
I confused by this line :
B action, finalField=42, field=0
Object B is not completely initialized and when we call method "action" from super class constructor - variable "field" has a default value, but the final variable "finalField" already has value 42.
When was the "finalField" initialized?
When a final field is initialized with a constant expression (15.29), it's called a constant variable (4.12.4):
private final int finalField = 42;
This means that the string
"B action, finalField=" + finalField + ", field="
is a constant expression itself and its value is determined at compile time. If you inspect the compiled class file you will in fact find the string B action, finalField=42, field=
in the constant pool section.
In general, when a field that is a constant variable is used, it has to be replaced by its value at compile time. It is not allowed (13.1) to reference the field at run time:
A reference to a field that is a constant variable (§4.12.4) must be resolved at compile time to the value V denoted by the constant variable's initializer.
If such a field is non-static, then no reference to the field should be present in the code in a binary file, except in the class containing the field. (It will be a class rather than an interface, since an interface has only static fields.) The class should have code to set the field's value to V during instance creation (§12.5).
The field initializer still runs when you would expect: after A
constructor has returned and before B
constructor starts. Observing the uninitialized value is tricky because the compiler inlines the uses of the variable, but you can access the value of the field through reflection:
public void action() {
try {
System.out.println("B action, finalField="
+ getClass().getDeclaredField("finalField").get(this)
+ ", field=" + field);
} catch (IllegalAccessException | NoSuchFieldException e) {
e.printStackTrace();
}
}
Output:
A constructor before
B action, finalField=0, field=0
A constructor after
B constructor
B action, finalField=42, field=99
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