语境
我们正在为 Scala 程序开发静态验证器(早期工作在 this Master's thesis 中描述),目前的重点是验证涉及惰性求值的 Scala 功能。我们主要对感兴趣语义 (行为)特征,而不是其他(尽管重要)方面,例如可理解性或简洁性。
为了简化事情,我们暂时忽略了单例对象可能具有的特殊角色。例如,有些是伴随对象(这可能与其惰性性质正交),或者有些是包对象。
惰性值和单例对象的属性
懒惰的 vals
假设一个惰性值
lazy val v = I
哪里
I
是初始化块,即确定惰性 val 值的代码。初始化块 I
当惰性 val v
时执行第一次取消引用。单例对象
假设一个单例对象
object Foo {
C1
val v1 = I1
var v2 = I2
lazy val v3 = I3
def m() {C2}
}
哪里
C1
是构成对象Foo
的构造函数的代码,其中 I1
至 I3
又是初始化块,其中 C2
是方法的主体 m
.当对象Foo
首先使用(取消引用,或分配给变量/字段),然后 C1, I1
和 I2
被执行。 I3
仅在 Foo.v3
时执行被取消引用(因为 v3
是一个惰性值)和 C2
每当 m
时执行叫做。题
考虑这个版本的
Foo
,其中单例对象已由惰性 val 和匿名类编码:// Should probably sit in a package object
lazy val Foo = new {
C1
val v1 = I1
var v2 = I2
lazy val v3 = I3
def m() {C2}
}
谁能想到为什么要对单例对象进行编码的原因
Foo
因为惰性 val 会显示与原始单例对象不同的行为?也就是说,是否存在编码版本与原始代码具有不同语义的(角落)情况?
最佳答案
至少有一个主要的语义差异,我在下面解释了这一点。我认为在多线程上下文中存在一些极端情况,因为对象是在其类的静态初始化程序期间创建的,并且静态初始化程序与锁有一种奇怪的交互。但我不是JVM多线程专家,所以我不知道细节。
无论如何,即使在单线程上下文中也会发生一些事情(并且至少在 Manifest
s 的实现中使用,并且可能是 ClassTag
s,在当前标准库中)。
考虑以下两个对象:
object A {
val other = B
}
object B {
val other = A
}
这是有效的 Scala 代码,可以访问
A.other
或 B.other
将正确返回另一个。这是因为 A
的“val”在调用 super 构造函数后立即初始化。本质上,您可以以某种方式将上述对象转换为这个“低级”代码:private var instanceA: AClass = null
def A(): AClass = {
if (instanceA == null)
new AClass
instanceA
}
class AClass {
def this() = {
// reification of the constructor of A
super()
instanceA = this
this.other = B()
}
}
当然,
B
类似翻译。你可以看到,当时 B()
被调用来初始化 A.other
,全局变量instanceA
已经初始化。因此,当 B
已创建并访问 A()
初始化 B.other
, def A()
将立即返回 A
的正确实例.lazy val
s 没有这个早期的初始化,所以编码对象很简单 lazy val
s 不会飞。访问任何一个对象都将“死锁”,唯一的线程等待自己。
关于scala - 将单例对象编码为惰性值,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24870207/