【问题标题】:Scala: constructor orders in inheritanceScala:继承中的构造函数顺序
【发布时间】:2017-08-16 13:23:25
【问题描述】:

有人可以详细解释一下在scala中继承调用构造函数的顺序吗?说我有:

abstract class A {
   private var data: T = compute()
   protected def compute(): T
}

class ImpA extends A {
   var a = 0
   override def compute() {
      a = 1
      null.asInstanceOf[T] // doesn't matter
   }
}

val inst = new ImpA

然后看起来inst.a == 0,所以我猜发生的情况是,当ImpA的构造函数被调用时,A的构造函数也被调用,这实际上触发了compute()应该设置a = 1 .但随后 scala 回到ImpA 的构造函数并重置a = 0。是这样吗?

是否有一些众所周知的模式可以正确避免这种情况? (我并没有真正尝试解决这个可以轻松处理的问题,尽管如果有建议的模式我很想知道它们;但我更想深入了解正在发生的事情,并且希望知道为什么重新初始化变量a 可能会对这种情况感兴趣。如果它是val,内部会发生什么,因为如果保持逻辑,它将导致对相同变量的多个引用分配......) .

提前致谢。

编辑:当您更改 ImpA.a 并使用引用而不是 var 时,很有趣

class ImpA extends A {
   class B {
     var b = 0
   }
   val b = new B
   override def compute() {
      b.b += 1
      null.asInstanceOf[T] // doesn't matter
   }
}

然后它会抛出一个java.lang.NullPointerException,因为b 还没有被实例化。在Yuval Itzchakov 解决方案之后,它的编译结果如下:

  abstract class A extends Object {
      private[this] var data: Object = _;
      <accessor> private def data(): Object = A.this.data;
      <accessor> private def data_=(x$1: Object): Unit = A.this.data = x$1;
      protected def compute(): Object;
      def <init>(): test.A = {
        A.super.<init>();
        A.this.data = A.this.compute();
        ()
      }
    };
    class ImpA extends test.A {
      private[this] val b: test.ImpA$B = _;
      <stable> <accessor> def b(): test.ImpA$B = ImpA.this.b;
      override def compute(): Unit = {
        ImpA.this.b().b_=(ImpA.this.b().b().+(1));
        {
          (null: Object);
          ()
        }
      };
      override <bridge> <artifact> def compute(): Object = {
        ImpA.this.compute();
        scala.runtime.BoxedUnit.UNIT
      };
      def <init>(): test.ImpA = {
        ImpA.super.<init>();
        ImpA.this.b = new test.ImpA$B(ImpA.this);
        ()
      }
    };
    class ImpA$B extends Object {
      private[this] var b: Int = _;
      <accessor> def b(): Int = ImpA$B.this.b;
      <accessor> def b_=(x$1: Int): Unit = ImpA$B.this.b = x$1;
      <synthetic> <paramaccessor> <artifact> protected val $outer: test.ImpA = _;
      <synthetic> <stable> <artifact> def $outer(): test.ImpA = ImpA$B.this.$outer;
      def <init>($outer: test.ImpA): test.ImpA$B = {
        if ($outer.eq(null))
          throw null
        else
          ImpA$B.this.$outer = $outer;
        ImpA$B.super.<init>();
        ImpA$B.this.b = 0;
        ()
      }
    }

虽然正确理解有点困难,但它非常直截了当地解释了为什么会抛出 NullPointerException

但是如果你这次使用lazy val b = new B,那么它可以工作:

class ImpA extends test.A {
  @volatile private[this] var bitmap$0: Boolean = false;
  private def b$lzycompute(): test.ImpA$B = {
    {
      ImpA.this.synchronized({
        if (ImpA.this.bitmap$0.unary_!())
          {
            ImpA.this.b = new test.ImpA$B(ImpA.this);
            ImpA.this.bitmap$0 = true;
            ()
          };
        scala.runtime.BoxedUnit.UNIT
      });
      ()
    };
    ImpA.this.b
  };
  lazy private[this] var b: test.ImpA$B = _;
  <stable> <accessor> lazy def b(): test.ImpA$B = if (ImpA.this.bitmap$0.unary_!())
    ImpA.this.b$lzycompute()
  else
    ImpA.this.b;
  override def compute(): Unit = {
    ImpA.this.b().b_=(ImpA.this.b().b().+(1));
    {
      (null: Object);
      () 
    }
  };
  override <bridge> <artifact> def compute(): Object = {
    ImpA.this.compute();
    scala.runtime.BoxedUnit.UNIT
  };
  def <init>(): test.ImpA = {
    ImpA.super.<init>();
    ()
  }
};

【问题讨论】:

  • 编辑:没关系,我太笨了,无法阅读...compute() 不会在任何地方调用。你刚刚定义了它。
  • @StefanFischer computeAs 构造函数中被调用。

标签: scala


【解决方案1】:

让我们看看编译器在编译时会生成什么(使用-Xprint:jvm 标志):

class ImpA extends com.testing.A {
  private[this] var a: Int = _;
  <accessor> def a(): Int = ImpA.this.a;
  <accessor> def a_=(x$1: Int): Unit = ImpA.this.a = x$1;
  override def compute(): String = {
    ImpA.this.a_=(1);
    (null: String)
  };
  override <bridge> <artifact> def compute(): Object = ImpA.this.compute();
  def <init>(): com.testing.ImpA = {
    ImpA.super.<init>();
    ImpA.this.a = 0;
    ()
  }
};

我们看到了什么?我们看到为ImplA(定义为&lt;init&gt;方法)运行的构造函数首先调用了ImpA.super.&lt;init&gt;(),这是对A的调用,首先进行了自身的初始化。 As 初始化代码如下:

def <init>(): com.testing.A = {
  A.super.<init>();
  A.this.data = A.this.compute();
  ()
}

它调用A.super,即Object,然后调用A.this.compute()。此方法初始化a 以保存值1。一旦初始化完成,ImplAa 设置为0,正如您在构造函数初始化期间告诉它的那样。这就是为什么您看到 0 对应于 a 的原因。

总结一下,执行流程如下:

  1. ImplA 调用 As 初始化方法
  2. A 调用 compute,在 ImplA 上调用
  3. ImplA.compute 分配 a1
  4. ImplA 分配 a0

欲了解更多信息,请参阅http://docs.scala-lang.org/tutorials/FAQ/initialization-order.html

【讨论】:

  • 谢谢 我什至没有想到要使用-Xprint:jvm 标志!我会看看我的编辑会发生什么,并自己理解 NPE! =)
  • @som-snytt 谢谢!它确实有助于理解。我还发现将其设置为 lazy val(但需要将 var a 放入绑定类)也可以正常工作(请参阅我对问题的编辑)。
猜你喜欢
  • 1970-01-01
  • 2013-01-29
  • 2017-05-13
  • 2023-04-04
  • 2011-11-24
  • 2018-09-10
  • 1970-01-01
相关资源
最近更新 更多