为什么对对象的 val 的 scala 测试断言会抛出 NullPointerException? [英] Why a scala test assertion on an object's val throws NullPointerException?
问题描述
在测试 Scala 代码时,我在对来自对象的值进行断言时遇到了一个奇怪的 NPE.
这是重现问题的最少代码:
main/scala/Playground.scala:
object Playground extends App {val 问候 = 你好 Scala"打印(问候)}
test/scala/PlaygroundSpec.scala:
import org.scalatest.wordspec._类 PlaygroundSpec 扩展了 AnyWordSpec {游乐场代码"应该 {打个招呼"在 {assert(Playground.greeting.contains("Hello"))//抛出 NPE,因为 greeting 为空.如何???}}}
示例程序运行良好并打印hello Scala",但测试在断言行抛出 NullPointerException,因为 greeting
为 null
.
如果用字符串常量初始化greeting
怎么可能是null
?
注意:在 val
声明中添加 lazy
使其工作并且测试通过.
In Scala 2 App
extends DelayedInit
,所以编译器神奇地重写了初始化代码,这样字段的初始化就移动到了 delayedInit
方法例如,
object Playground 扩展了 App {val 问候 = 你好 Scala"打印(问候)}
变得像
object Playground 扩展了 App {私有变量问候语:String = null定义问候():字符串 = 问候def delayInit(): Unit = {问候 = 你好 Scala"打印(问候())}def main(args: Array[String]) = {//间接调用delayInit...}}
现在我们可以看到
assert(Playground.greeting.contains(Hello"))
变成
assert(null.contains(Hello"))
as delayedInit
方法没有被调用.为了证明这一点,请观察以下内容的工作原理
Playground.main(Array.empty)//delayInit 被间接调用assert(Playground.greeting.contains("Hello"))//好的
<块引用>
在 val 声明中添加惰性使其工作并且测试通过.
这是可行的,因为lazy val greeting
有效地将字段转换为一种方法,将其移出初始化代码,因此它不会成为delayedInit
的一部分.
显然这令人困惑,所以 Scala 3 丢弃:Delayedinit.
While testing Scala code, I've run into a strange NPE while asserting on a value from an object.
Here is the minimal code to reproduce the issue:
main/scala/Playground.scala:
object Playground extends App {
val greeting = "Hello Scala"
println(greeting)
}
test/scala/PlaygroundSpec.scala:
import org.scalatest.wordspec._
class PlaygroundSpec extends AnyWordSpec {
"The playground code" should {
"say hello" in {
assert(Playground.greeting.contains("Hello")) // Throws NPE because greeting is null. How???
}
}
}
The sample program run just fine and prints "hello Scala", but the test throws a NullPointerException on the assertion line, because greeting
is null
.
How could greeting
be null
if it is initialized with a string constant?
Note: Adding
lazy
to theval
declaration makes it work and the test passes.
In Scala 2 App
extends DelayedInit
, so compiler magically rewrites initialisation code such that the initialisation of fields is moved to delayedInit
method, for example,
object Playground extends App {
val greeting = "Hello Scala"
println(greeting)
}
becomes something like
object Playground extends App {
private var greeting: String = null
def greeting(): String = greeting
def delayedInit(): Unit = {
greeting = "Hello Scala"
println(greeting())
}
def main(args: Array[String]) = {
// indirectly call delayedInit
...
}
}
Now we can see
assert(Playground.greeting.contains("Hello"))
becomes
assert(null.contains("Hello"))
as delayedInit
method did not get called. To prove the point observe how the following works
Playground.main(Array.empty) // delayedInit gets indirectly called
assert(Playground.greeting.contains("Hello")) // ok
Adding lazy to the val declaration makes it work and the test passes.
This works because lazy val greeting
effectively turns the field into a method which moves it out of the initialisation code so it does not become part of delayedInit
.
Clearly this is confusing so Scala 3 Dropped: Delayedinit.
这篇关于为什么对对象的 val 的 scala 测试断言会抛出 NullPointerException?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!