简体   繁体   English

更深入的解释为何Lazy Vals在Scala构造函数中起作用?

[英]A deeper explanation of why Lazy Vals work in scala constructors?

I understand the general use of Lazy vals to get around initialization order problems in scala, but something has always bothered me about this explanation. 我了解Lazy val通常用于解决Scala中的初始化顺序问题,但对于这种解释我一直感到困扰。 If a "Lazy Val" is initialized during it's first access, and the parent constructor is making use of it BEFORE it could possibly exist - what exactly is going on here? 如果“ Lazy Val”在其首次访问期间被初始化,并且其父构造方法在它可能存在之前就在使用它-究竟发生了什么? In the below example, when "println("A: " + x1)" is called - Class B doesn't exist yet.. but the value does correctly print. 在下面的示例中,当调用“ println(” A:“ + x1)”时-类B还不存在..但是该值可以正确打印。 At the exact moment we see "A: Hello" - did this happen in the constructor of A, or delayed somehow until B fully existed? 在确切的时刻,我们看到“ A:您好”-这是在A的构造函数中发生的,还是以某种方式延迟到B完全存在之前? In a sense, marking it "Lazy" has counter-intuitively made it available ahead of schedule? 从某种意义上说,将其标记为“ Lazy”是否违反直觉地提前提供了它?

Thank you 谢谢

(referenced from https://github.com/paulp/scala-faq/wiki/Initialization-Order ) (参考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"    

}

The object itself doesn't exist, but fields within the object can exist and be calculated. 对象本身不存在,但是对象中的字段可以存在并可以计算。

What's happening is that from within A's constructor, it's accessing x1 and therefore forcing the lazy value to be computed. 发生的是,它从A的构造函数中访问x1,因此强制要计算惰性值。 The reason A can know it needs to call B's x1 method, is because it's dynamically dispatched (just like in Java). A之所以知道需要调用B的x1方法,是因为它是动态调度的(就像Java中一样)。

If it helps, the stack would be something similar to this: 如果有帮助,堆栈将类似于以下内容:

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

If it helps, here is a rough version of your code in Java: 如果有帮助,这是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();
        }
    }

}

The "BEFORE" relation just refers to the order that initializers are run. “之前”关系仅指初始化程序运行的顺序。

When you allocate an object on the heap, you simply allocate it and then call the init methods to init it. 在堆上分配对象时,只需分配它,然后调用init方法将其初始化。

There's no sense in which there is an instance of parent A that precedes an instance of child B. 毫无疑问,在子B的实例之前有父A的实例。

They are the same object, seen as the parts of its type. 它们是同一对象,被视为其类型的一部分。

It's not like when people tell me I look like my father (who is not me). 就像别人告诉我我长得像父亲(不是我)一样。

Anyway, fragility ensues if it's not laziness all the way down: 无论如何,如果不是一直懒惰,就会出现脆弱性:

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)
}

With the result: 结果:

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

That's why the general advice is to use defs instead of vals and, for that matter, traits instead of classes ( to ensure since with traits you are more likely to have heard of and followed the first rule). 这就是为什么通常的建议是使用defs而不是vals,就此而言,使用traits而不是class( 以确保 使用traits时,您更有可能听说并遵循第一个规则)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM