Порядок инициализации выбрасывает нулевой указатель на доступ с отложенным доступом - PullRequest
3 голосов
/ 10 мая 2019

Ожидается, что следующий порядок инициализации без ленивого 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.x в Bar Конструктор:

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

Однако, почему нулевой указатель также выбрасывается, когда x ленивый , как это

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

там, где мне кажется, это должно работать из-за bitmap$0 охранника

    <stable> <accessor> lazy def x(): Int = if (Bar.this.bitmap$0.unary_!())
      Bar.this.x$lzycompute()
    else
      Bar.this.x;

Проверка доступа к полевым полям во время выполнения -Xcheckinit, кажется, удовлетворяет на моей машине с Scala 2.12.8, так почему NullPointerException когда lazy val x?

1 Ответ

4 голосов
/ 10 мая 2019

Я не думаю, что этот 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.

Кстати, если вы положите все в Hello с помощью метода main, вы получите StackOverflow вместо 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
   }

}
...