奇怪的scala编译没有失败但给出了错误的答案

odd scala compile do not failed but give the wrong answer

我有以下 Scala 代码。我把 CLASS_TYPE 放在 MAX_DATA_PAGE_SIZE 定义的后面是错误的。我以为这个编译不了,或者能给出正确答案,结果输出0,为什么会这样?

object hello extends App{
    val baseSize = 256
    val MIN_DATA_PAGE_SIZE = math.max(baseSize, 1024)
    val MAX_DATA_PAGE_SIZE =  MIN_DATA_PAGE_SIZE * scala.math.pow(2, CLASS_TYPE-1).toInt
    val CLASS_TYPE: Int = 2

    println(MAX_DATA_PAGE_SIZE)
}

编译器在编译时给了你一个警告,比如

[warn] Reference to uninitialized value CLASS_TYPE
[warn]   val MAX_DATA_PAGE_SIZE =  MIN_DATA_PAGE_SIZE * scala.math.pow(2, CLASS_TYPE-1).toInt
[warn]                                                                    ^
[warn] one warning found

解决此问题的最佳方法是使用更严格的编译器选项。添加到您的 build.sbt

scalacOptions ++= Seq(
"-unchecked",
"-deprecation",
"-feature",
"-Xfatal-warnings",
"-language:postfixOps",
"-Ywarn-unused-import"
)

另请注意,根据 scala style,您应该注意

等常量
MaxDataPageSize
ClassType

等等。

你是对的,由于使用了未初始化的变量,这不会用像 C++ 这样的语言编译。正如 Dmitry 的回答所提到的那样,编译器确实会警告您。但是,scala 会像这样编译为 Java:

~$ scalac -print hello.scala

hello.scala:4: warning: Reference to uninitialized value CLASS_TYPE
    val MAX_DATA_PAGE_SIZE =  MIN_DATA_PAGE_SIZE * scala.math.pow(2, CLASS_TYPE-1).toInt
                                                                     ^
[[syntax trees at end of                   cleanup]] // hello.scala
package <empty> {
  object hello extends Object with App {
    <stable> <accessor> def executionStart(): Long = hello.this.executionStart;
    @deprecatedOverriding("executionStart should not be overridden", "2.11.0") private[this] val executionStart: Long = _;
    final <accessor> def _args(): Array[String] = hello.this._args;
    private[this] var _args: Array[String] = _;
    final <accessor> def _args_=(x: Array[String]): Unit = {
      hello.this._args = x;
      ()
    };
    final <stable> <accessor> def initCode(): scala.collection.mutable.ListBuffer = hello.this.initCode;
    private[this] val initCode: scala.collection.mutable.ListBuffer = _;
    <accessor> def scala$App$_setter_$executionStart_=(x: Long): Unit = {
      hello.this.executionStart = x;
      ()
    };
    <accessor> def initCode_=(x: scala.collection.mutable.ListBuffer): Unit = {
      hello.this.initCode = x;
      ()
    };
    @deprecatedOverriding("args should not be overridden", "2.11.0") def args(): Array[String] = scala.App$class.args(hello.this);
    @deprecated("The delayedInit mechanism will disappear.", "2.11.0") override def delayedInit(body: Function0): Unit = scala.App$class.delayedInit(hello.this, body);
    @deprecatedOverriding("main should not be overridden", "2.11.0") def main(args: Array[String]): Unit = scala.App$class.main(hello.this, args);
    private[this] val baseSize: Int = _;
    <stable> <accessor> def baseSize(): Int = hello.this.baseSize;
    private[this] val MIN_DATA_PAGE_SIZE: Int = _;
    <stable> <accessor> def MIN_DATA_PAGE_SIZE(): Int = hello.this.MIN_DATA_PAGE_SIZE;
    private[this] val MAX_DATA_PAGE_SIZE: Int = _;
    <stable> <accessor> def MAX_DATA_PAGE_SIZE(): Int = hello.this.MAX_DATA_PAGE_SIZE;
    private[this] val CLASS_TYPE: Int = _;
    <stable> <accessor> def CLASS_TYPE(): Int = hello.this.CLASS_TYPE;
    final <synthetic> def delayedEndpoint$hello: Unit = {
      hello.this.baseSize = 256;
      hello.this.MIN_DATA_PAGE_SIZE = scala.math.`package`.max(hello.this.baseSize(), 1024);
      hello.this.MAX_DATA_PAGE_SIZE = hello.this.MIN_DATA_PAGE_SIZE().*(scala.math.`package`.pow(2.0, hello.this.CLASS_TYPE().-(1).toDouble()).toInt());
      hello.this.CLASS_TYPE = 2;
      scala.this.Predef.println(scala.Int.box(hello.this.MAX_DATA_PAGE_SIZE()));
      ()
    };
    def <init>(): hello.type = {
      hello.super.<init>();
      scala.App$class./*App$class*/$init$(hello.this);
      hello.this.delayedInit(new hello$delayedInit$body(hello.this));
      ()
    }
  };
  final <synthetic> class hello$delayedInit$body extends runtime.AbstractFunction0 {
    <paramaccessor> private[this] val $outer: hello.type = _;
    final def apply(): Object = {
      hello$delayedInit$body.this.$outer.delayedEndpoint$hello();
      scala.runtime.BoxedUnit.UNIT
    };
    def <init>($outer: hello.type): hello$delayedInit$body = {
      if ($outer.eq(null))
        throw null
      else
        hello$delayedInit$body.this.$outer = $outer;
      hello$delayedInit$body.super.<init>();
      ()
    }
  }
}

one warning found

这不会导致编译错误,但肯定会未定义的行为

如果你将 class 加载到 REPL 中并且 运行 它两次它会给出正确的答案,因为 CLASS_TYPE 已经正确初始化。

scala> :load -v hello.scala
Loading hello.scala...

scala> object hello extends App{
     |     val baseSize = 256
     |     val MIN_DATA_PAGE_SIZE = math.max(baseSize, 1024)
     |     val MAX_DATA_PAGE_SIZE =  MIN_DATA_PAGE_SIZE * scala.math.pow(2, CLASS_TYPE-1).toInt
     |     val CLASS_TYPE: Int = 2
     | 
     |     println(MAX_DATA_PAGE_SIZE)
     | }
<console>:10: warning: Reference to uninitialized value CLASS_TYPE
           val MAX_DATA_PAGE_SIZE =  MIN_DATA_PAGE_SIZE * scala.math.pow(2, CLASS_TYPE-1).toInt
                                                                            ^
defined object hello

scala> hello.main(Array())
0

scala> hello.main(Array())
2048

这与编译扩展 App 特征的对象的方式有关。

如果你写了一个像这样的普通应用程序:

object hello {

def main(args:Array[String]) {
    val baseSize = 256
    val MIN_DATA_PAGE_SIZE = math.max(baseSize, 1024)
    val MAX_DATA_PAGE_SIZE =  MIN_DATA_PAGE_SIZE * scala.math.pow(2, CLASS_TYPE-1).toInt
    val CLASS_TYPE: Int = 2

    println(MAX_DATA_PAGE_SIZE)
  }
}

并尝试编译它,您会收到以下错误:

~$ scalac hello.scala
hello.scala:6: error: forward reference extends over definition of value MAX_DATA_PAGE_SIZE
val MAX_DATA_PAGE_SIZE =  MIN_DATA_PAGE_SIZE * scala.math.pow(2, CLASS_TYPE-1).toInt
                                                                         ^
one error found