为什么不能使每个Scala实例变量成为一个懒惰的初始化变量?
|
除了增加冗长度外,还有其他强大的原因,为什么不应该声明每个实例变量应该被懒惰地初始化?
解决方法
首先:如果一个懒惰的val(如访问一个不存在的外部资源)出现问题,您只会注意到它是第一次访问val,而正常的val会尽快注意到因为正在构建对象.您还可以在延迟的vals中具有循环依赖性,这将导致类无法正常工作(其中一个可怕的NullPointerExceptions),但您只会在第一次访问所连接的延迟值之一时发现.
所以懒惰的vals使程序不那么确定性,这总是一件坏事. 第二:有一个延迟val的运行时开销.一个懒惰的val目前由一个私有的位掩码(int)在一个类中使用延迟值实现(对于每个延迟值一个位),所以如果你有超过32个惰性值将有两个位掩码等) 为了确保延迟初始化器只能运行一次,当字段被初始化时,存在对位掩码的同步写入,并且每次访问该字段时都会发生易失性读取.现在,x86架构上的易读性非常便宜,但是易失性的写入可能真的很贵. 据我所知,在未来版本的scala中进行优化,但总是会有一个开销来检查该字段是否被初始化,而不是直接访问.例如,懒惰访问的额外代码可能会阻止方法内联. 当然,对于非常小的类,位掩码的内存开销也可能是相关的. 但即使你没有任何性能问题,很明显vals依赖于彼此的顺序,只是按照顺序排列,并使用正常的vals. 编辑:这里是一个代码示例,说明如果您使用延迟vals,您可能会得到的非确定性: class Test {
lazy val x:Int = y
lazy val y:Int = x
}
你可以创建一个这个类的实例没有任何问题,但是一旦你访问x或y,你将得到一个StackOverflow.这当然是一个人为的例子.在现实世界中,你有更长的和不明显的依赖循环. 这是一个Scala控制台会话,它使用:javap来说明延迟val的运行时开销.首先是正常的val: scala> class Test { val x = 0 }
defined class Test
scala> :javap -c Test
Compiled from "<console>"
public class Test extends java.lang.Object implements scala.ScalaObject{
public int x();
Code:
0: aload_0
1: getfield #11; //Field x:I
4: ireturn
public Test();
Code:
0: aload_0
1: invokespecial #17; //Method java/lang/Object."<init>":()V
4: aload_0
5: iconst_0
6: putfield #11; //Field x:I
9: return
}
现在懒惰的val: scala> :javap -c Test
Compiled from "<console>"
public class Test extends java.lang.Object implements scala.ScalaObject{
public volatile int bitmap$0;
public int x();
Code:
0: aload_0
1: getfield #12; //Field bitmap$0:I
4: iconst_1
5: iand
6: iconst_0
7: if_icmpne 45
10: aload_0
11: dup
12: astore_1
13: monitorenter
14: aload_0
15: getfield #12; //Field bitmap$0:I
18: iconst_1
19: iand
20: iconst_0
21: if_icmpne 39
24: aload_0
25: iconst_0
26: putfield #14; //Field x:I
29: aload_0
30: aload_0
31: getfield #12; //Field bitmap$0:I
34: iconst_1
35: ior
36: putfield #12; //Field bitmap$0:I
39: getstatic #20; //Field scala/runtime/BoxedUnit.UNIT:Lscala/runtime/BoxedUnit;
42: pop
43: aload_1
44: monitorexit
45: aload_0
46: getfield #14; //Field x:I
49: ireturn
50: aload_1
51: monitorexit
52: athrow
Exception table:
from to target type
14 45 50 any
public Test();
Code:
0: aload_0
1: invokespecial #26; //Method java/lang/Object."<init>":()V
4: return
}
正如你所看到的,正常的val访问器非常短,一定是内联的,而懒惰的val访问器是相当复杂的(最重要的是并发)涉及一个同步块(monitorenter / monitorexit指令).您还可以看到由编译器生成的额外字段. (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |
