2017-08-16 101 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'放入綁定類)工作得很好(請參閱我對該問題的編輯)。 –