2

可能重复:
Scala 和前向引用

为什么以下在 Scala 中有效:

版本 1

object Strange extends App {
  val x = 42
  Console.println(x) // => outputs "42", as expected
}

版本 2

object Strange extends App {
  Console.println(x) // => "0" ?!
  val x = 42
}

为什么它完全编译,为什么在没有任何警告或任何警告的情况下表现得如此奇怪?

这也是同样的问题class

class StrangeClass {
  Console.println(x) // => still "0"
  val x = 42
}

object TestApp extends App {
  new StrangeClass()
}

常规方法的主体没有这样的问题:

def nonStrangeMethod {
  Console.println(y) // => fails with "not found: value y", as expected
  y = 42
}

如果我们将“final”添加到 val 声明中,行为会发生巨大变化:

class StrangeClass {
  Console.println(x) // => "42", but at least that's expected
  final val x = 42
}

作为记录,以下 Java 静态(Scala 的object)对应物:

public class Strange {
    static {
        System.out.println(x);
    }
    static int x = 42;

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

class在第 3 行和 Java 非静态(Scala's )对应项上出现简单且可理解的错误“无法在定义之前引用字段”的编译失败:

public class Strange {
    Strange() {
        System.out.println(x);
        int x = 42;
    }

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

显然在第 3 行出现“x 无法解析为变量”而失败。

4

2 回答 2

3

这是因为App使用延迟初始化的特性。来自Scala中关于App特征的编程:

花括号之间的代码被收集到单例对象的主构造函数中,并在类初始化时执行

2.9.0 版本也说明了这一点:

继承 App trait 的对象使用 Scala 2.9 的延迟初始化特性来执行整个主体作为继承的 main 方法的一部分。

所以在你得到它作为输出的地方运行Console.println(x)之前不会执行:Strange

scala> s.main(Array[String]())
0

如果您Console.println(x)在之后添加另一个,val x = 42则会打印出:

scala> s.main(Array[String]())
0
42

编译器知道它x存在于当前作用域中,但它会延迟对它的评估,直到它被执行,然后它会打印出它的默认值Intis 0

于 2012-11-22T02:13:29.943 回答
2

答案本质上是编译器如何构造事物。由于 Scala 中没有静态变量-初始化基本上发生在构造函数中,因此第二个示例的 Java 等效项类似于:

public class Strange {
    int x = 0;

    public Strange() {
        System.out.println(x);
        x = 42;
    }
}

哪个编译得很好。我假设编译器将未初始化的 int 的值设置为 0 以避免 NPE。如果您颠倒构造函数中语句的顺序,您将获得您在第一个示例中描述的行为。

这个问题有更多细节:Scala and forward references

于 2012-11-22T02:11:53.857 回答