我有这样一段代码,从一个文件加载属性:
class Config {
val properties: Properties = {
val p = new Properties()
p.load(Thread.currentThread().getContextClassLoader.getResourceAsStream("props"))
p
}
val forumId = properties.get("forum_id")
}
这看起来工作得很好。
我尝试过将properties
的初始化移到另一个瓦尔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条答案
按热度按时间mnemlml81#
Vals按照声明的顺序初始化(准确地说,non-lazyvals是这样),所以
properties
在loadedProps
之前初始化,或者换句话说,当properties
初始化时,loadedProps
仍然是null
,这里最简单的解决方案是在properties
之前定义loadedProps
:您也可以将
loadedProps
设置为lazy,这意味着它将在第一次访问时初始化:使用惰性瓦尔的优点是代码对于重构来说更加健壮,因为仅仅更改val的声明顺序不会破坏代码。
同样在这个特定的示例中,您可以将
loadedProps
转换为def
(如@NIA所建议),因为它只使用一次。svgewumm2#
我认为这里
loadedProps
可以通过简单地将val
替换为def
来简单地转换为函数:在这种情况下,您可以确保在调用它时它已被调用。
但不确定这是否是该病例的“模式”。
vatpfxk53#
只是添加了一点更多的解释:
你的
properties
字段在这里比loadedProps
字段初始化的早。null
是字段初始化前的值--这就是你得到它的原因。在def
的情况下,它只是一个方法调用,而不是访问某个字段,所以一切都很好(因为方法的代码可能被调用多次-这里没有初始化)。请参见http://docs.scala-lang.org/tutorials/FAQ/initialization-order.html。您可以使用def
或lazy val
来修复它为什么
def
如此不同?这是因为def
可以被调用几次,但是val
只能被调用一次(所以它的第一次也是唯一一次调用实际上是初始化字段)。lazy val
只有在调用时才能初始化,因此它也会有所帮助。另一个更简单的例子是:
一般来说,理论上scala可以分析字段之间的依赖关系(哪个字段需要其他字段),然后从最后一个节点开始初始化,但实际上每个模块都是单独编译的,编译器甚至可能不知道这些依赖关系(甚至可能是Java,它调用Scala,Scala调用Java),所以它只是顺序初始化。
因此,它甚至不能检测简单的循环:
实际上,这样的循环(在一个模块内)理论上可以在单独的构建中检测到,但它不会有多大帮助,因为它是非常明显的。
clj7thdc4#
正如其他人评论的那样,这是Scala中一个令人不安的问题,即使是在新的Scala 3上。
作为一种可能的解决方法,您可以使用
def
或lazy val
,如上所述,但它们甚至会带来其他后果和可能的性能影响。此外,作为另一种选择,您可以激活一个名为
-Xcheckinit
的Scala编译器标志,以发出有关此类错误的警告...遗憾的是,它不能捕获所有初始化错误,并且额外添加了过多的插装代码,这严重影响了性能,因此只建议用于本地开发。有关它的更多信息,请访问-〉https://docs.scala-lang.org/tutorials/FAQ/initialization-order.html