Expectedly, the following initialisation order without lazy val throws null pointer exception
class Foo {
Bar.x // NullPointerException
}
object Bar extends Foo {
val x = 42
}
object Hello extends App {
Bar
}
Examining -Xprint:jvm
output, and referencing @paradigmatic answer, we see this is due to Foo
's constructor running first and calling Bar.x()
before Bar.this.x
is initialised in Bar
's constructor:
class Foo extends Object {
def <init>(): example.Foo = {
Foo.super.<init>();
Bar.x();
()
}
};
object Bar extends example.Foo {
private[this] val x: Int = _;
<stable> <accessor> def x(): Int = Bar.this.x;
def <init>(): example.Bar.type = {
Bar.super.<init>();
Bar.this.x = 42;
()
}
};
However, why is null pointer also thrown when x
is lazy like so
object Bar extends Foo {
lazy val x = 42
}
Analysing -Xprint:jvm
output in lazy case we have
class Foo extends Object {
def <init>(): example.Foo = {
Foo.super.<init>();
Bar.x();
()
}
};
object Bar extends example.Foo {
final <synthetic> lazy private[this] var x: Int = _;
@volatile private[this] var bitmap$0: Boolean = _;
private def x$lzycompute(): Int = {
Bar.this.synchronized(if (Bar.this.bitmap$0.unary_!())
{
Bar.this.x = (42: Int);
Bar.this.bitmap$0 = true
});
Bar.this.x
};
<stable> <accessor> lazy def x(): Int = if (Bar.this.bitmap$0.unary_!())
Bar.this.x$lzycompute()
else
Bar.this.x;
def <init>(): example.Bar.type = {
Bar.super.<init>();
()
}
};
where it seems to me it should work due to the bitmap$0
guard
<stable> <accessor> lazy def x(): Int = if (Bar.this.bitmap$0.unary_!())
Bar.this.x$lzycompute()
else
Bar.this.x;
Runtime field accessors check -Xcheckinit
seems to be satisfied on my machine with Scala 2.12.8, so why NullPointerException
when lazy val x
?
I don't think this NPE is related to val
at all. Check this:
class Foo {
Bar.anyMethod
}
object Bar extends Foo {
def anyMethod = ???
}
object Hello extends App {
Bar
}
//java.lang.NullPointerException
Foo
is trying to run constructor on Bar
while Bar
is still under construction. So that's what your Foo
is doing too before calling x
.
Btw if you put everything into Hello
with main
method you will get StackOverflow instead of NPE in both mine and your cases.
object Hello {
def main(args: Array[String]): Unit = {
class Foo {
Bar.anyMethod
}
object Bar extends Foo { //<- Bar is like local val now instead of field
def anyMethod= ??? // of package object, so stack is available now.
}
Bar
}
}