I stripped down the original code even more leaving the original behavior intact. I also renamed some methods and vals to express the semantics better (mostly function vs value):
trait A {
println("in A")
def overridableComputation = {
println("A::overridableComputation")
1
}
val stubbableValue = overridableComputation
def stubbableMethod = overridableComputation
}
class StubB extends A {
println("in StubB")
val usefulVal = "super useful" //<<---this is the val that ends up being null
override def overridableComputation = {
println("StubB::overridableComputation")
println("usefulVal = " + usefulVal)
2
}
}
When run it yields the following output:
in A
StubB::overridableComputation
usefulVal = null
in StubB
super useful
Here are some Scala implementation details to help us understand what is happening:
- the main constructor is intertwined with the class definition, i.e. most of the code (except method definitions) between curly braces is put into the constructor;
- each
val
of the class is implemented as a private field and a getter method, both field and method are named after val (JavaBean convention is not adhered to);
- the value for the
val
is computed within the constructor and is used to initialize the field.
As m-z already noted, the initialization runs top down, i.e. the parent's class or trait constructor is called first, the child's constructor is called last. So here's what happens when you call new StubB()
:
- A
StubB
object is allocated in heap, all its fields are set to default values depending on their types (0
, 0.0
, null
, etc);
A::A
is invoked first as the top-most constructor;
"in A"
is printed;
- in order to compute the value for
stubbableValue
overridableComputation
is called, the catch is in fact that the overridden method is called, i.e. StubB::overridableComputation
see What's wrong with overridable method calls in constructors? for more details;
- "StubB::overridableComputation" is printed;
- since
usefulVal
is not yet initialized by StubB::StubB
it's default value is used, so "usefulVal = null" is printed;
2
is returned;
stubbableValue
is initialized with the computed value of 2
;
StubB::StubB
is invoked as the next constructor in chain;
"in StubB"
is printed;
- the value for
usefulVar
is computed, in this case just the literal "super useful"
is used;
usefulVar
is initialized with the value of "super useful"
.
Since the value for stubbableValue
is computed during constructor run
To prove these assumptions fernflower Java decompiler can be used. Here's how the above Scala code looks when decompiled to Java (I removed irrelevant @ScalaSignature
annotations):
import scala.collection.mutable.StringBuilder;
public class A {
private final int stubbableValue;
public int overridableComputation() {
.MODULE$.println("A::overridableComputation");
return 1;
}
public int stubbableValue() {
return this.stubbableValue;
}
public int stubbableMethod() {
return this.overridableComputation();
}
public A() {
.MODULE$.println("in A");
// Note, that overridden method is called below!
this.stubbableValue = this.overridableComputation();
}
}
public class StubB extends A {
private final String usefulVal;
public String usefulVal() {
return this.usefulVal;
}
public int overridableComputation() {
.MODULE$.println("StubB::overridableComputation");
.MODULE$.println(
(new StringBuilder()).append("usefulVal = ")
.append(this.usefulVal())
.toString()
);
return 2;
}
public StubB() {
.MODULE$.println("in StubB");
this.usefulVal = "super useful";
}
}
In case A
is a trait
instead of a class
the code is a bit more verbose, but behavior is consistent with the class A
variant. Since JVM doesn't support multiple inheritance Scala compiler splits a trait
into a abstract helper class which only contains static members and an interface:
import scala.collection.mutable.StringBuilder;
public abstract class A$class {
public static int overridableComputation(A $this) {
.MODULE$.println("A::overridableComputation");
return 1;
}
public static int stubbableMethod(A $this) {
return $this.overridableComputation();
}
public static void $init$(A $this) {
.MODULE$.println("in A");
$this.so32501595$A$_setter_$stubbableValue_$eq($this.overridableComputation());
}
}
public interface A {
void so32501595$A$_setter_$stubbableValue_$eq(int var1);
int overridableComputation();
int stubbableValue();
int stubbableMethod();
}
public class StubB implements A {
private final String usefulVal;
private final int stubbableValue;
public int stubbableValue() {
return this.stubbableValue;
}
public void so32501595$A$_setter_$stubbableValue_$eq(int x$1) {
this.stubbableValue = x$1;
}
public String usefulVal() {
return this.usefulVal;
}
public int overridableComputation() {
.MODULE$.println("StubB::overridableComputation");
.MODULE$.println(
(new StringBuilder()).append("usefulVal = ")
.append(this.usefulVal())
.toString()
);
return 2;
}
public StubB() {
A$class.$init$(this);
.MODULE$.println("in StubB");
this.usefulVal = "super useful";
}
}
Remember that a val
is rendered into a field and a method? Since several traits can be mixed into a single class, a trait cannot be implemented as a class. Therefore, the method part of a val
is put into an interface, while a field part is put into the class that a trait gets mixed into.
The abstract class contains the code of all the trait's methods, access to the member fields is provided by passing $this
explicitly.