初始化顺序在延迟 val 访问时抛出空指针

Initialisation order throws null pointer on lazy val access

不出所料,以下没有lazy val的初始化顺序会抛出空指针异常

class Foo {
  Bar.x // NullPointerException
}

object Bar extends Foo {
  val x = 42
}

object Hello extends App {
  Bar
}

检查 -Xprint:jvm 输出并引用@paradigmatic answer,我们看到这是由于 Foo 的构造函数 运行 首先调用 Bar.x()Bar.this.xBar 的构造函数中初始化之前:

  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;
      ()
    }
  };

但是,为什么当xlazy时也会抛出空指针

object Bar extends Foo {
  lazy val x = 42
}

在惰性情况下分析 -Xprint:jvm 输出我们有

  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[=13=]: Boolean = _;
    private def x$lzycompute(): Int = {
      Bar.this.synchronized(if (Bar.this.bitmap[=13=].unary_!())
        {
          Bar.this.x = (42: Int);
          Bar.this.bitmap[=13=] = true
        });
      Bar.this.x
    };
    <stable> <accessor> lazy def x(): Int = if (Bar.this.bitmap[=13=].unary_!())
      Bar.this.x$lzycompute()
    else
      Bar.this.x;
    def <init>(): example.Bar.type = {
      Bar.super.<init>();
      ()
    }
  };

在我看来,由于 bitmap[=22=] 守卫

,它应该可以工作
    <stable> <accessor> lazy def x(): Int = if (Bar.this.bitmap[=14=].unary_!())
      Bar.this.x$lzycompute()
    else
      Bar.this.x;

运行时字段访问器检查 -Xcheckinit 在我的机器上使用 Scala 2.12.8 似乎很满意,所以为什么 NullPointerExceptionlazy val x?

我认为这个 NPE 与 val 完全无关。检查这个:

class Foo {
  Bar.anyMethod
}

object Bar extends Foo {
  def anyMethod = ???
}

object Hello extends App {
  Bar
}

//java.lang.NullPointerException

Foo 正在 Bar 上尝试 运行 构造函数,而 Bar 仍在构造中。这就是你的 Foo 在调用 x 之前所做的事情。

顺便说一句,如果您使用 main 方法将所有内容都放入 Hello 中,您将获得 Whosebug 而不是我和您的案例中的 NPE。

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
   }

}