我进一步剥离了原始代码,使原始行为保持不变。我还重命名了一些方法和 val 以更好地表达语义(主要是 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
}
}
运行时会产生以下输出:
in A
StubB::overridableComputation
usefulVal = null
in StubB
super useful
以下是一些 Scala 实现细节,可帮助我们了解正在发生的事情:
- 主构造函数与类定义交织在一起,即大括号之间的大部分代码(方法定义除外)都放入构造函数中;
- 类的每个
val都实现为私有字段和getter方法,字段和方法均以val命名(不遵守JavaBean约定);
-
val 的值在构造函数中计算并用于初始化字段。
正如 m-z 已经指出的那样,初始化自上而下运行,即首先调用父类或特征构造函数,最后调用子构造函数。所以当你打电话给new StubB()时会发生这种情况:
-
StubB 对象分配在堆中,其所有字段根据其类型(0、0.0、null 等)设置为默认值;
-
A::A 作为最顶层的构造函数首先被调用;
-
"in A" 已打印;
- 为了计算
stubbableValue 的值overridableComputation 被调用,关键在于调用了被覆盖的方法,即StubB::overridableComputation 更多细节见What's wrong with overridable method calls in constructors?;
- “StubB::overridableComputation”被打印;
- 由于
usefulVal 还没有被StubB::StubB 初始化,所以使用了默认值,所以打印了“usefulVal = null”;
-
2 被返回;
-
stubbableValue 使用 2 的计算值初始化;
-
StubB::StubB 被调用为链中的下一个构造函数;
-
"in StubB" 已打印;
- 计算
usefulVar 的值,在这种情况下只使用文字"super useful";
-
usefulVar 被初始化为"super useful"。
因为stubbableValue 的值是在构造函数运行期间计算的
为了证明这些假设fernflower可以使用Java反编译器。以下是上述 Scala 代码反编译为 Java 时的样子(我删除了不相关的 @ScalaSignature 注释):
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";
}
}
如果A 是trait 而不是class,则代码有点冗长,但行为与class A 变体一致。由于 JVM 不支持多重继承,Scala 编译器将 trait 拆分为一个只包含静态成员和接口的抽象辅助类:
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";
}
}
还记得val 被渲染成一个字段和一个方法吗?由于可以将多个特征混合到一个类中,因此一个特征不能作为一个类来实现。因此,val 的方法部分被放入接口,而字段部分被放入 trait 混入的类中。
抽象类包含所有 trait 方法的代码,通过显式传递 $this 提供对成员字段的访问。