2017-08-16 78 views
1

有人能详细解释一下我在scala中继承调用构造函数的顺序吗?说我有: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。但后来斯卡拉回落到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>(); 
    () 
    } 
}; 
+0

编辑:没关系,我太傻看... '计算()'不被任何调用。你刚刚定义它。 –

+0

@StefanFischer'compute'在'A'构造函数中被调用。 –

回答

2

让我们来看看编译(使用-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(定义为<init>方法)的构造函数首先调用ImpA.super.<init>(),这是对A的初始化的初始化。 A的初始化代码如下所示:

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

它调用A.super,这是Object,然后调用A.this.compute()。该方法初始化a以保持值1.初始化完成后,ImplAa设置为0,正如您在构造函数初始化期间所做的那样。这就是为什么你看到a的值0

综上所述,执行流程如下:

  1. ImplA呼叫A的init方法
  2. A呼叫compute,这是在ImplA
  3. ImplA.compute受让人a1
  4. 调用
  5. ImplA分配a0

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

+0

谢谢我甚至没有想过要使用'-Xprint:jvm'标志!我会看看我的编辑会发生什么,然后自己理解NPE! =) –

+0

http://docs.scala-lang.org/tutorials/FAQ/initialization-order.html –

+0

@ som-snytt谢谢!它有助于理解。我还发现将它设置为'lazy val'(但需要将'var a'放入绑定类)工作得很好(请参阅我对该问题的编辑)。 –