具有和不具有类型归属的最终VAL的Scala不一致行为

具有和不具有类型归属的最终VAL的Scala不一致行为,scala,Scala,我使用的是scala 2.10.3,我注意到了以下行为 object TestConstantScala { final val str1 : String = "foo:" + number1 final val number1 : Int = 123 final val number2 : Int = 123 final val str2 : String = "foo:" + number2 def main(args: Array[String

我使用的是
scala 2.10.3
,我注意到了以下行为

object TestConstantScala {

    final val str1 : String = "foo:" + number1
    final val number1 : Int = 123
    final val number2 : Int = 123
    final val str2 : String = "foo:" + number2

    def main(args: Array[String]){
        System.out.println(str1)
        System.out.println(str2)
    }


}
输出:

foo:0  
foo:123  

我的问题是,为什么订单会起作用。此外,如果我省略了
Int
定义,它将返回正常行为,没有类型归属(
:Int
number1甚至不作为字段存在,因此不需要初始化。相反,编译器创建一个直接返回值
123
的访问器方法,并在构造函数中使用文本值
123
初始化
str1

当存在类型归属时,为什么要创建字段?在这种情况下这真的没有意义,但有时类型归属可能需要执行值转换的代码,比如装箱原语或应用隐式转换。出于语义原因(对象标识、隐式转换的副作用)和效率考虑,这些操作只能执行一次。因此,结果必须存储在字段中

因此,没有类型归属的行为是对初始化为常量值的
final
基元字段的优化,并且编译器没有足够的智能在存在类型归属时应用优化

下面是一个更简单的示例:

object TestConstantScala {
  final val brokenStr: String = "foo:" + brokenNumber
  final val brokenNumber: Int = 123
  final val workingStr: String = "foo:" + workingNumber
  final val workingNumber = 123

  println(brokenStr)
  println(workingStr)
}
下面是
scalac-Xprint:constructors
的输出,显示了将初始化移到构造函数中之后的AST:

[[syntax trees at end of              constructors]] // test18.scala
package <empty> {
  object TestConstantScala extends Object {
    final private[this] val brokenStr: String = _;
    final <stable> <accessor> def brokenStr(): String = TestConstantScala.this.brokenStr;
    final private[this] val brokenNumber: Int = _;
    final <stable> <accessor> def brokenNumber(): Int = TestConstantScala.this.brokenNumber;
    final private[this] val workingStr: String = _;
    final <stable> <accessor> def workingStr(): String = TestConstantScala.this.workingStr;
    final <stable> <accessor> def workingNumber(): Int(123) = 123;
    def <init>(): TestConstantScala.type = {
      TestConstantScala.super.<init>();
      TestConstantScala.this.brokenStr = "foo:".+(scala.Int.box(TestConstantScala.this.brokenNumber()));
      TestConstantScala.this.brokenNumber = 123;
      TestConstantScala.this.workingStr = "foo:".+(scala.Int.box(123));
      scala.this.Predef.println(TestConstantScala.this.brokenStr());
      scala.this.Predef.println(TestConstantScala.this.workingStr());
      ()
    }
  }
}
[[构造函数末尾的语法树]]///test18.scala
包装{
对象TestConstantScala扩展对象{
final private[this]val brokenStr:String=\uux;
final def brokenStr():String=TestConstantScala.this.brokenStr;
final private[this]val brokenNumber:Int=\uux;
final def brokenNumber():Int=TestConstantScala.this.brokenNumber;
final private[this]val workingStr:String=\uu0;
final def workingStr():String=TestConstantScala.this.workingStr;
最终def工作编号():Int(123)=123;
def():TestConstantScala.type={
TestConstantScala.super.();
TestConstantScala.this.brokenStr=“foo:”.+(scala.Int.box(TestConstantScala.this.brokenNumber());
TestConstantScala.this.brokenNumber=123;
TestConstantScala.this.workingStr=“foo:”.+(scala.Int.box(123));
scala.this.Predef.println(TestConstantScala.this.brokenStr());
scala.this.Predef.println(TestConstantScala.this.workingStr());
()
}
}
}

请注意,
workingNumber
没有字段,只有一个访问器,构造函数中的
workingStr
是如何用
“foo:”.+(scala.Int.box(123))

初始化的,有两个问题:

  • 初始化顺序(正如@wingedsubmariner正确提到的)
  • 常量值定义(和不是优化)。这就是如何在使用符号值而不是文字时获得有效的模式匹配(Java以同样的方式重载
    final
    关键字)
来自Scala语言规范,第4.1节值声明和定义

常量值定义的形式为

其中e是一个常数表达式(§6.24)。最后一个修饰符必须存在并且 不能给出类型注释。对常量值x的引用本身就是 作为常量表达式处理;在生成的代码中,它们被定义的 右侧e

至于顺序——这与java中的行为相同——首先,值被初始化为它们的默认值(0表示int,null表示引用类型等等),因此在初始化str1时,初始化为0,而不是123(尚未)。真正有趣的是,为什么这种行为在有类型归属和没有类型归属的情况下是不同的
final val x = e