为什么不能使每个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指令).您还可以看到由编译器生成的额外字段. (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |