2017-08-16 9 views
1

スカラーの継承でコンストラクタが呼び出される順序について、誰かが私に詳しく説明できますか?私が持っていると言う:スカラ:継承のコンストラクタ命令

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コンストラクタが実際にa = 1を設定する必要がありcompute()をトリガする、とも呼ばれていることであると思います。しかし、スカラはImpAのコンストラクタに戻り、a = 0をリセットします。それですか?

これを正しく回避するためのよく知られたパターンがありますか? (私は本当に簡単に対処できるこの問題を解決しようとしているわけではありませんが、アドバイスされたパターンがあれば私はそれらを知りたいと思っていますが、何が起こっているのかを深く理解したいと思っています。うまくいけば、変数aを再初期化することがなぜそのような場合に興味があるのか​​知っていればいいのですが、論理が保持されていれば同じ変数に複数の参照を割り当てるようになるので... valであれば、

ありがとうございます。

EDIT:あなただけ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 
    } 
} 

bがまだインスタンス化されていないので、それはjava.lang.NullPointerExceptionをスローします。 NullPointerExceptionがスローされた理由、それはかなり素直に説明し、それが適切に理解することは少し難しいですけれども

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

Yuval Itzchakovソリューションに続き、ここではそれがにコンパイルものです。

しかし、あなたは、この時間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 sの初期化コードは次のようになります。それはA.this.compute()を呼び出し、その後Objectである、A.superを呼び出し、

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

。このメソッドは、aを初期化して値1を保持します。初期化が完了すると、ImplAa0に設定します。これはコンストラクタの初期化中に指示したとおりです。そのため、値0aになります。要約する

は、実行フローは以下の通りである:

  1. ImplAコールA S initメソッドImplA
  2. ImplA.compute割り当てるa1
  3. に呼び出され
  4. Aコールcompute
  5. ImplAはの値を割り当てます0

の詳細については、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'をバインディングクラスに入れる必要があります)。 –

関連する問題