前向引用 - 为什么这段代码会编译?

时间:2011-10-14 03:49:40

标签: scala initialization lazy-evaluation

请考虑以下代码段:

 object A {
     val b = c
     val c = "foo"
 }
 println( A.b )   // prints "null"

作为更大程序的一部分,这将导致运行时失败。编译器显然允许从'b'到(未初始化)'c'的前向引用,但是'b'留下c的原始空值。为什么允许这样做?是否有可以从此功能中受益的编程方案?

将代码更改为直接序列并且行为更改:

 val b = c
 val c = "foo"
 println( b )   // prints "foo"

为什么行为不同?为什么这甚至有效?感谢。

更新1:

问题出现了我如何运行第二个例子。我简化了设置并使用最新的Scala插件在IntelliJ IDEA 10.5.2中使用Scala 2.9.0.1编译它。这是一个确切的代码,在一个新创建的和其他空项目中,我用来测试它,在这种环境下编译和运行良好:

 package test
 object Main { 
    def main( args: Array[String] ) {
       val b = c
       val c = "foo"
       println( b )   // prints "foo"
    }
 }

对于它的价值,IDEA还认为(当我点击“通过”对val b = c中的'c'的引用时)我指的是(c)的(后面)声明。

2 个答案:

答案 0 :(得分:17)

类或对象的主体是主要构造函数。构造函数与方法一样,是按顺序执行的一系列语句 - 要做其他任何事情,它必须是一种非常不同的语言。我很确定你不希望Scala以顺序执行任何其他顺序的方法语句。

这里的问题是类和对象的主体也是成员的声明,这是你混淆的根源。您会看到val声明正是这样的:一种声明式编程形式,如Prolog程序或XML配置文件。但它们实际上是两件事:

// This is the declarative part
object A {
  val b
  val c
}

// This is the constructor part
object A {
  b = c
  c = "foo"
}

您问题的另一部分是您的示例非常简单。这是一种特殊情况,其中某种行为似乎有意义。但考虑一下:

abstract class A {
  def c: String
}

class B extends A {
  val b = c
  override val c = "foo"
}

class C extends { override val c = "foobar" } with B

val x = new C
println(x.b)
println(x.c)

您期望发生什么?构造函数执行的语义保证了两件事:

  1. 可预测性。一开始你可能会觉得它不直观,但规则很明确,而且相对容易理解。
  2. 子类可以依赖于已经初始化的超类(因此,它的方法可用)。
  3. <强>输出:

    它将打印“foobar”两次以获得更多=&gt; https://docs.scala-lang.org/tutorials/FAQ/initialization-order.html

答案 1 :(得分:1)

这是因为Scala的过时版本。

使用Scala 2.11.5编译时会发出警告或根本不编译:

C:\Users\Andriy\Projects\com\github\plokhotnyuk>scala
Welcome to Scala version 2.11.5 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_31).
Type in expressions to have them evaluated.
Type :help for more information.

scala> { object A { val b = c; val c = "foo" }; println(A.b) }
<console>:9: warning: Reference to uninitialized value c
              { object A { val b = c; val c = "foo" }; println(A.b) }
                                   ^
null

scala> { val b = c; val c = "foo"; println(A.b) }
<console>:9: error: forward reference extends over definition of value b
              { val b = c; val c = "foo"; println(A.b) }
                        ^