12

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

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 中是否有这种场景的模式?

谢谢!

4

3 回答 3

21

Vals 是按照它们声明的顺序初始化的(嗯,准确地说,非惰性vals 是),所以propertiesloadedProps. 或者换句话说,loadedProps仍然是初始化的null时候。properties这里最简单的解决方案是定义loadedPropsbefore 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 
  }
}

使用惰性 val 的优点是您的代码对重构更加健壮,因为仅更改 val 的声明顺序不会破坏您的代码。

同样在这种特殊情况下,您可以loadedProps变成一个def(如@NIA 所建议的那样),因为它只使用一次。

于 2013-01-28T18:42:33.380 回答
5

我认为这里loadedProps可以简单地通过简单地替换为一个val函数def

private def loadedProps = {
  // Tons of code
}

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

但不确定它是否适合这种情况。

于 2013-01-28T17:54:53.940 回答
4

只是添加了更多解释:

您的字段比此处的properties字段更早初始化。是初始化之前的字段值 - 这就是你得到它的原因。如果它只是一个方法调用而不是访问某个字段,那么一切都很好(因为方法的代码可能会被调用多次 - 这里没有初始化)。请参阅http://docs.scala-lang.org/tutorials/FAQ/initialization-order.html。您可以使用或修复它loadedPropsnulldefdeflazy 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可以分析字段之间的依赖图(哪个字段需要其他字段)并从最终节点开始初始化。但实际上每个模块都是单独编译的,编译器甚至可能不知道这些依赖关系(甚至可能是 Java,它调用 Scala,它调用 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

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

于 2015-04-16T12:27:18.883 回答