the industrial

都内で働くITエンジニアの日記

ScalaのAbstractとTraitを継承した際の優先順位

抽象クラスとトレイト(あるいは複数のトレイト)を同時に継承した際に、それぞれで同じメソッドが存在した場合はどうなるのかな?って思い、気になったので調べてみた。

調べる前はコンパイルエラーになるものだと思っていたのだけど、そうでもなく、後に継承した方が勝ちになる様子。

なので、抽象クラスとトレイトで同じ名前のメソッドを定義・実装していた場合、抽象クラスの前にトレイトを継承定義できないので、トレイトのメソッドが必ず優先されることになる。

ついでに、継承周りのアレコレを見て、理解しておくためにメモメモ。やっぱこうやってわざわざブログに書くと覚える気がする!

ちなみにトレイトについては継承でなくミックスインと言うとのこと。

ソース

ってことで今回、下記の様なソースを書いて検証。

// 抽象クラス
abstract class Abstract {
  println(s"Abstract Constructor")
  def f01
  def f02 = println("Abstract f02")
  def f03
  def f04 = println("Abstract f04")
}

// トレイト1
trait Trait1 {
  println("Trait1 Constructor")
  def f01
  def f02 = println("Trait1 f02")
  def f03 = println("Trait1 f03")
  def f04

  def f05
  def f06 = println("Trait1 f06")
  def f07
  def f08 = println("Trait1 f08")
}

// トレイト2
trait Trait2 {
  println("Trait2 Constructor")
  def f05
  def f06 = println("Trait2 f06")
  def f07 = println("Trait2 f07")
  def f08
}

// 実行クラス
class Child extends Abstract with Trait1 with Trait2 {
  println("Child Constructor")
  // super.f01 // 定義するとコンパイルエラーになる(Abstract、Trait1ともに実装なし)
  super.f02   // Trait1に定義したf02が呼び出される(Abstract、Trait1ともに実装あり)
  super.f03   // Trait1に定義したf03が呼び出される
  super.f04   // Abstractに定義したf04が呼び出される
  // super.f05 // 定義するとコンパイルエラーになる(Trait1、Trait2ともに実装なし)
  super.f06   // Trait2に定義したf06が呼び出される(Trait1、Trait2ともに実装あり)
  super.f07   // Trait2に定義したf07が呼び出される
  super.f08   // Trait1に定義したf08が呼び出される

  def f01 = println("Child f01")           // Abstract、Trait1ともに実装が無いので、overrideではなく実装
  override def f02 = println("Child f02") // 要 override
  override def f03 = println("Child f03") // 要 override
  override def f04 = println("Child f04") // 要 override
  def f05 = println("Child f05")           // Abstract、Trait1ともに実装が無いので、overrideではなく実装
  override def f06 = println("Child f06") // 要 override
  override def f07 = println("Child f07") // 要 override
  override def f08 = println("Child f08") // 要 override
}

// 実行用
object Main extends App {
  // インスタンス生成により実行
  new Child
}

実行結果

そして結果。

Abstract Constructor
Trait1 Constructor
Trait2 Constructor
Child Constructor
Trait1 f02
Trait1 f03
Abstract f04
Trait2 f06
Trait2 f07
Trait1 f08

当たり前っちゃ当たり前の結果になるんだけど、下記の通りの結果。

  1. 継承の順序は、抽象クラス→トレイト1→トレイト2
  2. f03、f04、f07、f08について、実装されている方のメソッドが優先される(先に継承されたクラスのメソッドだったとしても)
  3. f02とf06について、抽象クラスとトレイト1(あるいはトレイト1とトレイト2)で、どちらも処理が実装されている場合、後に継承した方のメソッドが優先される
  4. f01とf05について、抽象クラスとトレイト1(あるいはトレイト1とトレイト2)で、どちらも処理が実装されていないメソッドが存在する場合、Childで実装する必要がある