我们正在研究Scala程序的静态验证程序(this Master's thesis中描述的早期工作),目前的重点在于验证涉及延迟评估的Scala功能。我们主要感兴趣的是功能的语义(行为),而不是其他(尽管很重要)方面,如可理解性或简洁性。
为简化起见,我们 - 暂时 - 忽略单个对象可能具有的特殊角色。例如,有些是伴随对象(可能与它们的懒惰性质正交),或者有些是包对象。
懒惰的
假设懒惰的val
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
是一个懒惰的val),并且只要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会显示出与原始单例对象不同的行为的原因吗?也就是说,是否存在编码版本与原始代码具有不同语义的(角落)情况?
答案 0 :(得分:0)
至少有一个主要的语义差异,我在下面解释过。我认为在多线程上下文中存在一些极端情况,因为对象是在其类的静态初始化器期间创建的,而静态初始化器与锁具有奇怪的交互。但我不是JVM多线程专家,所以我不知道细节。
无论如何,即使在单线程上下文中也会出现这种情况(至少在Manifest
的实现中使用了 ,也许ClassTag
s ,在当前的标准库中。)
考虑以下两个对象:
object A {
val other = B
}
object B {
val other = A
}
这是有效的Scala代码,访问A.other
或B.other
将正确返回另一个代码。这是因为A
的“val”在调用超级构造函数后立即初始化。从本质上讲,你可以某种方式将上述对象转换为这个“低级”代码:
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
没有这种早期初始化,因此将对象编码为简单lazy val
s不会飞。访问任何一个对象将“死锁”,只有线程等待自己。