gpt4 book ai didi

scala - Scala super 构造函数中的空值?

转载 作者:行者123 更新时间:2023-12-01 11:42:20 24 4
gpt4 key购买 nike

今天在处理更多面向对象的部分时,我得到了一些 NPE - 我不知道为什么,但在构造函数上为覆盖字段提供了空值。

有什么办法可以解决这个问题吗?这是一个演示该行为的示例。您会注意到基类的 println 会导致打印 null。如果基类尝试对基类的构造函数中的“line”执行任何操作,它将触发一个 npe。

scala> class Base {val line = "hey1"; println(line)}
defined class Base

scala> class Extended extends Base{ override val line = "hey2"; println(line)}
defined class Extended

scala> new Extended
null
hey2
res0: Extended = Extended@55991e21

scala> new Base
hey1
res1: Base = Base@1cc21a68

例如,这里是一个演示空指针异常的示例。
scala> class Base { val line = "hello"; println(line.reverse)}
defined class Base
^
scala> class Extend extends Base { override val line ="exthello"; println(line.reverse);}
defined class Extend

scala> new Extend
java.lang.NullPointerException

最佳答案

来源:https://github.com/paulp/scala-faq/wiki/Initialization-Order

(感谢 Ghik - 赞成评论或接受的答案而不是这个答案 - 为了完整性和历史记录,这里复制)。

初始化顺序
为什么我的抽象或覆盖的 val 为空?


考虑以下。

abstract class A {
val x1: String
val x2: String = "mom"

println("A: " + x1 + ", " + x2)
}
class B extends A {
val x1: String = "hello"

println("B: " + x1 + ", " + x2)
}
class C extends B {
override val x2: String = "dad"

println("C: " + x1 + ", " + x2)
}
// scala> new C
// A: null, null
// B: hello, null
// C: hello, dad

'strict' 或 'eager' val 是没有标记为惰性的。

在没有“早期定义”(见下文)的情况下,严格 val 的初始化按以下顺序完成。

父类(super class)在子类之前完全初始化。
否则,按声明顺序。
自然地,当一个 val 被覆盖时,它不会被初始化多次。因此,尽管上面示例中的 x2 似乎在每个点都定义了,但事实并非如此:在构造父类(super class)期间,重写的 val 将显示为 null,抽象 val 也是如此。

有一个编译器标志可用于识别这种情况:

-Xcheckinit:向字段访问器添加运行时检查。

不建议在测试之外使用此标志。通过在所有可能未初始化的字段访问周围放置一个包装器,它显着增加了代码大小:包装器将抛出异常而不是允许空值(或在原始类型的情况下为 0/false)静默出现。还要注意,这增加了一个运行时检查:它只能告诉您有关您在适当位置执行的代码路径的任何信息。

在开头示例中使用它:
% scalac -Xcheckinit a.scala
% scala -e 'new C'
scala.UninitializedFieldError: Uninitialized field: a.scala: 13
at C.x2(a.scala:13)
at A.<init>(a.scala:5)
at B.<init>(a.scala:7)
at C.<init>(a.scala:12)

避免空值的方法包括:

使用惰性值。
abstract class A {
val x1: String
lazy val x2: String = "mom"

println("A: " + x1 + ", " + x2)
}
class B extends A {
lazy val x1: String = "hello"

println("B: " + x1 + ", " + x2)
}
class C extends B {
override lazy val x2: String = "dad"

println("C: " + x1 + ", " + x2)
}
// scala> new C
// A: hello, dad
// B: hello, dad
// C: hello, dad

通常是最佳答案。不幸的是,你不能声明一个抽象的惰性 val。如果这就是您所追求的,您的选择包括:

声明一个抽象的严格 val,并希望子类将其实现为惰性 val 或具有早期定义。如果他们不这样做,在构造过程中的某些点上,它似乎是未初始化的。
声明一个抽象 def,并希望子类将它实现为一个惰性 val。如果他们不这样做,它将在每次访问时重新评估。
声明一个具体的惰性 val 抛出异常,并希望子类覆盖它。如果他们不这样做,它将......抛出异常。
惰性 val 初始化期间的异常将导致在下一次访问时重新评估右侧:请参阅 SLS 5.2。

请注意,使用多个惰性 val 会产生新的风险:惰性 val 之间的循环会导致首次访问时堆栈溢出。

使用早期定义。
abstract class A {
val x1: String
val x2: String = "mom"

println("A: " + x1 + ", " + x2)
}
class B extends {
val x1: String = "hello"
} with A {
println("B: " + x1 + ", " + x2)
}
class C extends {
override val x2: String = "dad"
} with B {
println("C: " + x1 + ", " + x2)
}
// scala> new C
// A: hello, dad
// B: hello, dad
// C: hello, dad

早期定义有点笨拙,早期定义块中可以出现的内容和可以引用的内容存在限制,并且它们的组合不如惰性 val:但如果惰性 val 不受欢迎,则它们提供了另一种选择.它们在 SLS 5.1.6 中指定。

使用常量值定义。
abstract class A {
val x1: String
val x2: String = "mom"

println("A: " + x1 + ", " + x2)
}
class B extends A {
val x1: String = "hello"
final val x3 = "goodbye"

println("B: " + x1 + ", " + x2)
}
class C extends B {
override val x2: String = "dad"

println("C: " + x1 + ", " + x2)
}
abstract class D {
val c: C
val x3 = c.x3 // no exceptions!
println("D: " + c + " but " + x3)
}
class E extends D {
val c = new C
println(s"E: ${c.x1}, ${c.x2}, and $x3...")
}
//scala> new E
//D: null but goodbye
//A: null, null
//B: hello, null
//C: hello, dad
//E: hello, dad, and goodbye...

有时,您需要的只是一个编译时常量。
常量值比严格的更严格,比早期的定义更早,并且有更多的限制,因为它们必须是常量。它们在 SLS 4.1 中指定。

关于scala - Scala super 构造函数中的空值?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18598610/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com