我有这段代码从文件加载属性:
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中是否存在此方案的模式?
谢谢!
答案 0 :(得分:19)
Vals按照声明的顺序进行初始化(确切地说,非延迟 vals),因此properties
在loadedProps
之前被初始化。或者换句话说,loadedProps
在null
初始化时仍为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。您可以使用def
或lazy 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
实际上,这样的循环(在一个模块内部)理论上可以在单独的构建中检测到,但它不会有太多帮助,因为它非常明显。