This is a nice puzzle related to the fine details of Java's final
field semantics, and instance creation semantics. Like any other field, a final
field is initialised to a default value when the object is created (JLS §4.12.5):
Every variable in a program must have a value before its value is used:
- Each class variable, instance variable, or array component is initialized with a default value when it is created (§15.9, §15.10.2):
The final
is merely a compile-time check to make sure that the field will be assigned a value exactly once, at the time the object is created. At runtime, the field still has its default initial value before it is assigned a new value. However, there is a special case for final
variables of primitive types or of type String
, whose initializer is a constant expression (§4.12.4):
A constant variable is a final variable of primitive type or type String that is initialized with a constant expression (§15.28). Whether a variable is a constant variable or not may have implications with respect to class initialization (§12.4.1), binary compatibility (§13.1, §13.4.9), and definite assignment (§16 (Definite Assignment)).
A constant expression is defined in (§15.28) to include literals (including String
literals), such as "B"
. The expression "B".trim()
is a method invocation expression, so it is not a constant expression.
The remaining detail is that when an instance of B
is created, the B()
constructor implicitly invokes the superclass constructor A()
, and this occurs before the rest of the B()
constructor or any initializers in the subclass B
are executed (§12.5, emphasis mine):
Just before a reference to the newly created object is returned as the result, the indicated constructor is processed to initialize the new object using the following procedure: ...
This constructor does not begin with an explicit constructor invocation of another constructor in the same class (using this). If this constructor is for a class other than Object, then this constructor will begin with an explicit or implicit invocation of a superclass constructor (using super). ...
Execute the instance initializers and instance variable initializers for this class, assigning the values of instance variable initializers to the corresponding instance variables, in the left-to-right order in which they appear textually in the source code for the class.
So in case 2, because the assignment x = "B".trim()
is in the subclass B
, it is not executed until after the A()
constructor has completed. Since the A()
constructor calls f()
, that method prints out a null
value because that's the default value of the field before it has been assigned any other value.
However, in case 1, the special rule for a final
field of type String
initialized with a constant expression applies, so x
is already "B"
before the A()
constructor is called.