class A {
val x = println("A")
}
class B extends A {
override val x = println("B")
}
(new B).x
打印:
A
B
然而,
class A {
lazy val x = println("A")
}
class B extends A {
override lazy val x = println("B")
}
(new B).x
只打印:
B
According to Martin Odersky,至少在非惰性情况下,行为是“指定的”。我很好奇为什么行为是这样指定的,以及为什么它在val是懒惰的时候会有所不同。
答案 0 :(得分:12)
在成员定义之外的类定义的模板(“body”)中的代码是构造函数中的代码。初始化子类的实例时,总是调用父类的构造函数(在这种情况下,您调用的是不带参数的构造函数,否则语法将为class B extends A(arg1, arg2) { ... }
)。有关更多详细信息,请参阅Scala Language Specification中的第5.1节。
这就是为什么在第一种情况下评估println("A")
的原因; val
定义是构造函数代码的一部分。
当您考虑第二个示例中施工时发生的情况时,您从未询问过x
中定义的A
的值;现在因为它是lazy val
,所以在需要它之前不会计算它。
您可以将lazy val
视为不带参数的方法,并在第一次调用它们时缓存它们的输出。你没有在这里调用那个方法,你只是定义了它。
答案 1 :(得分:1)
class A {
val x = println("A")
}
class B extends A {
override val x = println("B")
}
(new B).x
在执行构造函数期间,执行val
s(和其他语句)的所有初始化。因此,val x = println("A")
作为构建A
的一部分运行,override val x = println("B")
作为B
构建的一部分运行。它们都会打印一些字符串并将x
初始化为()
(类型为Unit
),因此覆盖只是一个红色的鲱鱼。 .x
(来自(new B).x
)无效。
在懒惰的情况下,您正在将x
初始化为类似于不带参数的函数并返回Unit
。在您阅读.x
之前不会运行,并且,由于x
被覆盖,因此在这种情况下只会打印“B”。