【问题标题】:Why do overridden variables get the wrong values in Scala?为什么在 Scala 中被覆盖的变量会得到错误的值?
【发布时间】:2018-04-01 05:12:39
【问题描述】:

我在 Scala 中有一个 A 类,比如这个:

class A {
  val a = 3
  lazy val b = 2
  println("a = " + a)
  println("b = " + b)
}

接下来,我将这个类扩展到另一个类B:

class B extends A {
  override val a = 4
  override lazy val b = 3
}

现在,当我创建class B 的对象时,我得到以下输出:

a = 0  //the default value of int is zero `0` in Scala
b = 3

而我期望输出是:

a = 3
b = 2

我的问题是class A 中的println() 函数如何知道class B 中定义的值,但只知道b 而不是a

【问题讨论】:

    标签: scala


    【解决方案1】:

    docs.scala-lang.org - tutorials - initialization-order 提供了完整的解释。


    为了看得更清楚,让我们在B 类中打印与A 类中相同的内容:

    class A {
      val a = 3
      lazy val b = 2
      println("A: a = " + a)
      println("A: b = " + b)
    }
    
    class B extends A {
      override val a = 4
      override lazy val b = 3
      println("B: a = " + a)
      println("B: b = " + b)
    }
    

    在这种情况下,new B() 产生:

    A: a = 0
    A: b = 3
    B: a = 4
    B: b = 3
    

    非惰性val变量的初始化顺序为:

    在没有“早期定义”(见下文)的情况下,初始化 严格的 vals 按以下顺序完成。

    1. 超类在子类之前完全初始化。
    2. 否则,按申报顺序。

    因此AB 之前完全初始化。

    但是val 不能被多次初始化。因此,在首先处理超类 A 时,Scala 在内部给它一个默认值(0IntnullString,..)。

    因此它首先打印0(初始化A)然后打印4(初始化B)。


    使用lazy vals 是scaladoc 提出的绕过这个限制的解决方案。

    【讨论】:

    • 感谢您的回复!这个答案解释了很多关于 Scala 中的继承。
    • 第一个 b = 3 有什么意义?在 A 的 println 时间,在 B 的覆盖之前运行,它访问当前设置为 2 的 b。
    • @allidoiswin 它在 B 初始化之前运行,但 在 B 覆盖之后。所有的代码都在那里,只是在A 的初始化期间没有为a 调用它,因为最终变量a 可以只分配一次。 lazy val 更像是缓存的def
    • 我不明白为什么 a 的 println 也不遵循“覆盖后运行”。您能否详细说明正在发生的事情?即像某某被初始化,某某有它的值集,等等?谢谢。
    【解决方案2】:

    为什么lazy val 按预期工作?因为vallazy 标记将在其第一次访问时被初始化。

    为什么 non-lazy 不能按预期工作?因为初始化顺序。

    非惰性值被初始化in the following order:

    1. 超类在子类之前完全初始化。
    2. 否则,按申报顺序。

    在您的示例中,您尝试访问 override val,但在构造超类期间,override val 将显示为 null

    包含更多日志的代码以查看初始化顺序:

    class A {
      println("First line of A")
      val strictVal: String = {
        println("A.strictVal called")
        "strictVal of A"
      }
      lazy val lazyVal: String = {
        println("A.lazyVal called")
        "lazyVal of A"
      }
    
      println(s"lazyVal = $lazyVal")
      println(s"strictVal = $strictVal")
    
      println("Last line of A")
    }
    
    class B extends A {
      println("First line of B")
      override val strictVal: String = {
        println("B.strictVal called")
        "strictVal of B"
      }
    
      override lazy val lazyVal: String = {
        println("B.lazyVal called")
        "lazyVal of B"
      }
      println("Last line of B")
    }
    

    new B() 的输出:

    First line of A
    A.strictVal called
    B.lazyVal called
    lazyVal = lazyVal of B
    strictVal = null
    Last line of A
    First line of B
    B.strictVal called
    Last line of B
    

    【讨论】:

    • 问题中描述的行为已经够让人迷惑了,你为什么把变量名全部换掉,让a变得懒惰……而且s"..."-string中的s插值似乎没有做任何事情?
    • @AndreyTyukin,谢谢,修复了这些并更改了示例。希望现在好多了
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-17
    • 2012-05-29
    • 2017-05-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多