更深入解释为什么Lazy Vals在scala构造函数中工作?

时间:2013-08-12 03:00:40

标签: scala

我理解Lazy vals的一般用法来解决scala中的初始化顺序问题,但是有些事总是困扰我这个解释。如果在第一次访问期间初始化了“Lazy Val”,并且父构造函数在它可能存在之前正在使用它 - 这究竟是什么?在下面的例子中,当调用“println(”A:“+ x1)”时 - B类尚不存在..但值正确打印。在我们看到“A:Hello”的确切时刻 - 这是在A的构造函数中发生的,还是以某种方式延迟直到B完全存在?从某种意义上说,将它标记为“懒惰”已经反直觉地使其提前提供了吗?

谢谢

(引自https://github.com/paulp/scala-faq/wiki/Initialization-Order

abstract class A {
  val x1: String

  println("A: " + x1)
}
class B extends A {
  lazy val x1: String = "hello"    

}

2 个答案:

答案 0 :(得分:2)

对象本身不存在,但对象中的字段可以存在并被计算。

正在发生的事情是,在A的构造函数中,它正在访问x1,因此强制计算延迟值。 A可以知道它需要调用B的x1方法的原因是因为它是动态调度的(就像在Java中一样)。

如果有帮助,那么堆栈将类似于:

B.x1$lzycompute
B.x1
A.<init>
B.<init>

如果有帮助,这里是Java代码的粗略版本:

public class Testing {

    public static void main(String[] args) {
        new B();
    }

    public static abstract class A {

        public abstract String x1();

        public A() {
            System.out.println(x1());
        }
    }

    public static class B extends A {
        private boolean inited = false;
        private String x1;

        private String computeX1() {
            x1 = "hello";
            inited = true;
            return x1;
        }

        public String x1() {
            return this.inited ? x1 : computeX1();
        }
    }

}

答案 1 :(得分:2)

“BEFORE”关系只是指运行初始值设定项的顺序。

在堆上分配对象时,只需分配它,然后调用init方法来初始化它。

在孩子B的实例之前有一个父A的实例是没有意义的。

它们是同一个对象,被视为其类型的部分。

当人们告诉我我看起来像我的父亲(不是我)时,情况并非如此。

无论如何,如果它不是一直懒惰的话,就会出现脆弱:

abstract class A {
  val x1: String
  val x2: String
  println("A: " + x1)
  println("A2: " + x2)
}
class B extends A {
  lazy val x1: String = "hello"
  lazy val x2: String = x3
  val x3: String = "bye"
}
object Test extends App {
  val b = new B
  Console println (b.x1,b.x2,b.x3)
}

结果:

A: hello
A2: null
(hello,null,bye)

这就是为什么一般的建议是使用def而不是vals,并且就此而言,使用traits而不是类(以确保,因为有了特征你更有可能听说过并遵循第一个规则)。