Scala strange behavior in class/object initializat

2019-02-25 19:30发布

问题:

Possible Duplicate:
Scala and forward references

Is there any rationale why the following works in Scala:

Version 1

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

Version 2

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

Why does it compile at all and why behaves so weird without any warnings or whatsoever?

It's also the same issue with class:

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

object TestApp extends App {
  new StrangeClass()
}

There are no such issue with regular method's body:

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

And behavior changes dramatically if we'd add "final" to val declaration:

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

For records, the following Java static (Scala's object) counterpart:

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

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

fails compilation with plain & understandable error "Cannot reference a field before it is defined" on line #3 and Java non-static (Scala's class) counterpart:

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

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

obviously fails with "x cannot be resolved to a variable" on line #3.

回答1:

It's because of the App trait which uses delayed initialization. From Programming in Scala regarding the App trait:

The code between the curly braces is collected into a primary constructor of the singleton object, and is executed when the class is initialized

The 2.9.0 release notes this too:

Objects inheriting the App trait instead make use of Scala 2.9’s delayed initialization feature to execute the whole body as part of an inherited main method.

So Console.println(x) is not executed until Strange is ran where you get this as output:

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

If you add another Console.println(x) after val x = 42 then it prints out:

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

The compiler knows that x exists in the current scope but it delays evaluation of it until it is executed and then it prints out the default value for Int which is 0.



回答2:

The answer is essentially how that compiler constructs things. Since there are no static variables in Scala - the initialization basically happens in the constructor, so the Java equivalent of your second example would be similar to:

public class Strange {
    int x = 0;

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

Which would compile fine. I assume the compiler sets the value of the uninitialized int to 0 to avoid a NPE. If you reversed the order of the statements in the constructor, you will get the behavior you were describing in the first example.

There is some more detail in this question: Scala and forward references