昨日と同じように、菱形継承問題を考える。
scala> trait TraitA { | def greet(): Unit | } defined trait TraitA scala> trait TraitB extends TraitA { | override def greet(): Unit = println("Good morning!") | } defined trait TraitB scala> trait TraitC extends TraitA { | override def greet(): Unit = println("Good evening!") | } defined trait TraitC
昨日と違うのは、TraitB と TraitC で greet メソッドを実装するときに override キーワードをつけているところだ。この場合、つぎのように、単純に2つを継承したクラスを作ってもエラーにならない。
scala> class ClassA extends TraitB with TraitC defined class ClassA
ここで、ClassA の greet メソッドを呼び出すと何を出力するか。試してみよう。
scala> (new ClassA).greet() Good evening!
Good evening! と表示された。ということは TraitC の greet メソッドが呼ばれたってことだ。
つぎに、TraitB と TraitC の継承順を入れ替えた ClassB を考えよう。
scala> class ClassB extends TraitC with TraitB defined class ClassB scala> (new ClassB).greet() Good morning!
今度は TraitB の greet メソッドが呼び出されている。
Scala では、こういう形で複数のトレイトを継承(ミックスイン)した場合、あとからミックスインしたトレイトが優先される。この機能をトレイトの線形化と呼ぶ。
さて、ここからがちょっとよくわからない。メソッドの中で super を使うことで親トレイトのメソッドを呼び出すことができる。定義はこんなふうだ。
scala> trait TraitA { | def greet(): Unit = println("Hello!") | } defined trait TraitA scala> trait TraitB extends TraitA { | override def greet(): Unit = { | super.greet() | println("My name is Terebi-chan.") | } | } defined trait TraitB scala> trait TraitC extends TraitA { | override def greet(): Unit = { | super.greet() | println("I like niconico.") | } | } defined trait TraitC scala> class ClassA extends TraitB with TraitC defined class ClassA scala> class ClassB extends TraitC with TraitB defined class ClassB
ClassA の greet メソッドを呼んでみよう。
scala> (new ClassA).greet() Hello! My name is Terebi-chan. I like niconico.
あとからミックスインされた TraitC の greet メソッドが呼ばれるはずだから I like niconico. と出力されるのはわかる。super を使って親トレイトの greet メソッドも呼んでいるんだから Hello! と出力されるのもわかる。だけどなんで My name is Terebi-chan. と出力されるんだ?これは TraitB のメソッドの出力のはずだろう?
TraitB と TraitC の間には親子関係がないはずなのに、まるで TraitB が TraitC の親トレイトであるかのようになっている。線形化ってそういうものなのか?
ClassC では出力の順番が変わる。
scala> (new ClassB).greet() Hello! I like niconico. My name is Terebi-chan.
線形化の順番が違うので出力の順番も違うってことだろう。とは思うけど、なんとなく納得がいかない。