我遇到了一个奇怪且令人费解的NPE。考虑以下用例:
编写一个通用算法(在我的例子中是二进制搜索),你想要概括这个类型,但需要一些额外的东西。
例如:您可能希望将范围缩小一半,并且需要通用half
或two
" consts"。
Integral
类型类是不够的,因为它只提供one
和zero
,所以我想出了:
trait IntegralConsts[N] {
val tc: Integral[N]
val two = tc.plus(tc.one,tc.one)
val four = tc.plus(two,two)
}
object IntegralConsts {
implicit def consts[N : Integral] = new IntegralConsts[N] {
override val tc = implicitly[Integral[N]]
}
}
并按如下方式使用:
def binRangeSearch[N : IntegralConsts]( /* irrelevant args */ ) = {
val consts = implicitly[IntegralConsts[N]]
val math = consts.tc
// some irrelevant logic, which contain expressions like:
val halfRange = math.quot(range, consts.two)
// ...
}
在运行时,这会在这一行引发令人费解的NullPointerException
:val two = tc.plus(tc.one,tc.one)
作为一种解决方法,我刚刚将lazy
添加到类型类' val
,这一切都成功了:
trait IntegralConsts[N] {
val tc: Integral[N]
lazy val two = tc.plus(tc.one,tc.one)
lazy val four = tc.plus(two,two)
}
但我想知道为什么我会得到这个奇怪的NPE。应该知道初始化顺序,到达tc
val two ...
应该已经实例化了
答案 0 :(得分:2)
初始化顺序应该是已知的,
时实例化tc
应该已经存在 到达val two
不符合规范。真正发生的是,在构建匿名类时,首先IntegralConsts[T]
将被初始化,只有才会在派生的anon类中撤消tc
的覆盖,这是为什么你会遇到NullPointerException
。
specification section §5.1 (Templates)说:
模板评估
考虑一个模板
sc with mt1 with mtn { stats }
。如果这是特质的模板,那么它的mixin评估包括对语句序列统计的评估。
如果这不是特质的模板,那么它的评估包括以下步骤:
- 首先,评估超类构造函数
sc
。- 然后,模板的线性化直到由
sc
表示的模板超类的所有基类进行混合评估。混合评估在线性化中以相反的顺序发生。- 最后评估语句序列
stats
。
我们可以通过使用-Xprint:typer
:
final class $anon extends AnyRef with IntegralConsts[N] {
def <init>(): <$anon: IntegralConsts[N]> = {
$anon.super.<init>();
()
};
private[this] val tc: Integral[N] = scala.Predef.implicitly[Integral[N]](evidence$1);
override <stable> <accessor> def tc: Integral[N] = $anon.this.tc
};
我们首先看到super.<init>
被调用,然后只有val tc
被初始化。
除此之外,我们来看看&#34; Why is my abstract or overridden val null?&#34;:
'严格'或'渴望'的val是一个没有标记为懒惰的。
在没有“早期定义”(见下文)的情况下,初始化 严格的vals按以下顺序完成:
- 超类在子类之前完全初始化。
- 否则,按声明顺序。
自然地,当覆盖
val
时,它不会被多次初始化......事实并非如此:在构造超类时,重写的val似乎为null,抽象val。
我们也可以通过将-Xcheckinit
标记传递给scalac
来验证这一点:
> set scalacOptions := Seq("-Xcheckinit")
[info] Defining *:scalacOptions
[info] The new value will be used by compile:scalacOptions
[info] Reapplying settings...
[info] Set current project to root (in build file:/C:/)
> console
> :pa // paste code here
defined trait IntegralConsts
defined module IntegralConsts
binRangeSearch: [N](range: N)(implicit evidence$2: IntegralConsts[N])Unit
scala> binRangeSearch(100)
scala.UninitializedFieldError: Uninitialized field: <console>: 16
at IntegralConsts$$anon$1.tc(<console>:16)
at IntegralConsts$class.$init$(<console>:9)
at IntegralConsts$$anon$1.<init>(<console>:15)
at IntegralConsts$.consts(<console>:15)
at .<init>(<console>:10)
at .<clinit>(<console>)
at .<init>(<console>:7)
at .<clinit>(<console>)
正如您所指出的,由于这是一个匿名类,因此将lazy
添加到定义中可以完全避免初始化怪癖。另一种方法是使用早期定义:
object IntegralConsts {
implicit def consts[N : Integral] = new {
override val tc = implicitly[Integral[N]]
} with IntegralConsts[N]
}