Scala - vals的初始化顺序

时间:2013-01-28 17:50:47

标签: scala constructor initialization immutability final

我有这段代码从文件加载属性:

class Config {
  val properties: Properties = {
    val p = new Properties()
    p.load(Thread.currentThread().getContextClassLoader.getResourceAsStream("props"))
    p
  }

  val forumId = properties.get("forum_id")
}

这似乎工作正常。

我已尝试将properties的初始化移动到另一个val loadedProperties,如下所示:

class Config {
  val properties: Properties = loadedProps
  val forumId = properties.get("forum_id")

  private val loadedProps = {
    val p = new Properties()
    p.load(Thread.currentThread().getContextClassLoader.getResourceAsStream("props"))
    p 
   }

}

但它不起作用! (properties)中{properties.get("forum_id")为空。

为什么会这样? loadedProps引用时是否评估properties

其次,这是初始化需要非平凡处理的变量的好方法吗?在Java中,我会声明它们final字段,并在构造函数中执行与初始化相关的操作。

Scala中是否存在此方案的模式?

谢谢!

3 个答案:

答案 0 :(得分:19)

Vals按照声明的顺序进行初始化(确切地说,非延迟 vals),因此propertiesloadedProps之前被初始化。或者换句话说,loadedPropsnull初始化时仍为properties。 这里最简单的解决方案是在loadedProps之前定义properties

class Config {
  private val loadedProps = {
    val p = new Properties()
    p.load(Thread.currentThread().getContextClassLoader.getResourceAsStream("props"))
    p 
  }
  val properties: Properties = loadedProps
  val forumId = properties.get("forum_id")
}

您也可以使loadedProps懒惰,这意味着它会在首次访问时初始化:

class Config {
  val properties: Properties = loadedProps
  val forumId = properties.get("forum_id")

  private lazy val loadedProps = {
    val p = new Properties()
    p.load(Thread.currentThread().getContextClassLoader.getResourceAsStream("props"))
    p 
  }
}

使用lazy val的优势在于您的代码对于重构更加健壮,因为仅仅更改val的声明顺序不会破坏您的代码。

同样在这个特殊情况下,您可以将loadedProps变成def(由@NIA建议),因为它只会被使用一次。

答案 1 :(得分:5)

我认为loadedProps只需将val替换为def即可简单地转换为函数:

private def loadedProps = {
  // Tons of code
}

在这种情况下,您确定在调用它时会调用它。

但不确定这种情况是模式

答案 2 :(得分:2)

只需添加一点解释:

您的properties字段在此处初始化loadedProps字段。 null是初始化之前的字段值 - 这就是您获取它的原因。在def情况下,它只是一个方法调用而不是访问某个字段,所以一切都很好(因为方法的代码可能被调用多次 - 这里没有初始化)。见http://docs.scala-lang.org/tutorials/FAQ/initialization-order.html。您可以使用deflazy val进行修复

为什么def如此不同?这是因为def可能被调用多次,但是val - 只调用一次(所以它的第一个也是唯一一个调用实际上是fileld的初始化)。

lazy val只有在您调用它时才能初始化,因此它也会有所帮助。

另一个更简单的例子:

scala> class A {val a = b; val b = 5}
<console>:7: warning: Reference to uninitialized value b
       class A {val a = b; val b = 5}
                        ^
defined class A

scala> (new A).a
res2: Int = 0 //null

更一般地说,理论上scala可以分析字段之间的依赖图(哪个字段需要其他字段)并从最终节点开始初始化。但实际上每个模块都是单独编译的,编译器可能甚至不知道那些依赖项(它甚至可能是调用Scala的Java调用Java),所以它只是进行顺序初始化。

因此,因此,它甚至无法检测到简单的循环:

scala> class A {val a: Int = b; val b: Int = a}
<console>:7: warning: Reference to uninitialized value b
       class A {val a: Int = b; val b: Int = a}
                             ^
defined class A

scala> (new A).a
res4: Int = 0

scala> class A {lazy val a: Int = b; lazy val b: Int = a}
defined class A

scala> (new A).a
java.lang.StackOverflowError

实际上,这样的循环(在一个模块内部)理论上可以在单独的构建中检测到,但它不会有太多帮助,因为它非常明显。