Scalaのトレイトでプログラマをミックスインしてやんよ:スケーラブルで関数型でオブジェクト指向なScala入門(7)(2/3 ページ)
Scalaの特徴を紹介し、基本構文や関数、クラスなど、Scalaの基本的な機能について解説する入門連載
別のトレイトで両方とも同じシグネチャ(名前)のメソッドを実装していた場合
意図せず複数実装したトレイトのシグネチャ(名前)が同じになってしまうこともあるかもしれません。そんなときには、どのような挙動になるでしょうか。下記のような2つのトレイトを用意して確認してみましょう。
trait Programmer { def write = println("コードを書きます") } trait Writer { def write = println("記事を書きます") }
どちらも引数、戻り値なしの「write」というメソッドを持っているトレイトです。この2つのトレイトをミックスインした場合はどうなるでしょうか? Personクラスを新しく定義し、ProgrammerとWriterをミックスインしてみましょう。
scala> class Person extends Programmer with Writer <console>:9: error: overriding method write in trait Programmer of type => Unit; method write in trait Writer of type => Unit needs `override' modifier class Person extends Programmer with Writer
エラーになってしまいました。同じシグネチャのメソッドを持つトレイトを複数ミックスインしたクラスでは、そのメソッドを必ずオーバーライドしなければいけません。
class Person extends Programmer with Writer { override def write = println("ドキュメントを書きます") }
writeメソッドをオーバーライドすればエラーもなくなり、実行できます。
scala> val p = new Person p: Person = Person@41d4702d scala> p.write ドキュメントを書きます
トレイトで「super」を使用するには
トレイトの場合でもクラスの継承のときと同じように、「super」キーワードを使えばミックスインしたトレイトのメソッドを呼び出せます。
では、Personクラスのオーバーライドしたwrite内でsuperを使用してミックスインしたトレイトのwriteを呼び出すと、どうなるでしょうか。
class Person extends Programmer with Writer { override def write = super.write }
この場合呼び出されるメソッドは、一番右にあるwithで指定されたトレイト(この場合「Writer」)のメソッドが呼び出されます。
scala> val p = new Person p: Person = Person@6e0d2a4e scala> p.write 記事を書きます
しかし、一番右に指定したトレイトのメソッドではなく、任意のトレイトのメソッドを呼び出したい場合もあると思います。そのような場合、以下の形式でメソッドを呼び出します。
super[トレイト名].メソッド
Programmerトレイトのwriteを呼び出してみましょう。
class Person extends Programmer with Writer { override def write = super[Programmer].write }
Programmerのwriteメソッドが呼ばれています。
scala> val p = new Person p: Person = Person@7eb79ed4 scala> p.write コードを書きます
トレイト単体での使用
いままではPersonクラスを使用してインスタンス化していましたが、トレイト単体でもインスタンス化できます。
下記の例では、Programmerトレイトを指定してインスタンス化しています。
scala> val p = new Programmer{} p: java.lang.Object with Programmer = $anon$1@1b704fb0 scala> p.write コードを書きます
トレイト名の後ろの「{}」は必須です。これは、Programmerトレイトをミックスインした無名クラスのインスタンスを作成することを意味しています。もし、Programmerトレイトのwriteが抽象メソッドだった場合、下記のようにインスタンス化のタイミングで実装できます。
trait Programmer { def write } scala> val p = new Programmer { def write = println("コーディングします") }
また、複数のトレイトをミックスインしたインスタンスも作成可能です(Javaでは、複数のインターフェイスを実装した無名クラスのインスタンスを作成できません)。
trait T1 { def t1 = println("t1") } trait T2 { def t2 = println("t2") } scala> val t = new T1 with T2 //複数のトレイトをミックスインしてインスタンス化 t: java.lang.Object with T1 with T2 = $anon$1@c25f12d
T1トレイトとT2トレイトをミックスインして無名クラスでインスタンス化しています。このように、トレイトはJavaのインターフェイス以上にいろいろな使い方が可能です。
積み重ね可能なトレイト
トレイトはいくつも積み重ねることができ、それによってメソッドの挙動を変更できます。
サンプルで使用するクラスとトレイトを定義しましょう。まずはすべてのクラスのベースとなる、Engineer抽象クラスを定義します。これはwork抽象メソッドを定義しています。
abstract class Engineer { println("class Engineer constructor") def work(time:Int) }
次にEngineerを継承したPersonを定義します。workクラスを定義しますが、これは渡された時間どおりにタスクを行います。
class Person extends Engineer{ println("class Person constructor") def work(time:Int) = { println("Person#work start") println("1つのタスクを" + time + "分で行います") println("Person#work end") } }
Personクラスをインスタンス化してworkメソッドを実行してみましょう。
scala> val p = new Person class Engineer constructor class Person constructor p: Person = Person@1412504b scala> p.work(60) Person#work start 1つのタスクを60分で行います Person#work end
workメソッドに渡した引数通りにtimeが表示されます。
「abstract override」キーワード
次に、Programmerトレイトを定義します。Programmerのworkメソッドは、渡された時間より15分短縮してタスクを行えます(15分以下のタスクについては考えないでください)。
trait Programmer extends Engineer { println("trait Programmer constructor") abstract override def work(time:Int) = { println("Programmer#work start") super.work(time - 15) println("Programmer#work end") } }
このトレイトをよく見てください。Engineer抽象クラスをスーパークラスに指定しています。これは、「Programmerトレイトをミックスインできるのは、Engineerを継承するクラスだけである」ということを示しています。
そして、もう1つの特徴は「abstract宣言されたメソッドの中でsuperの呼び出しを行っている」ということです。さて、このsuperは何を指しているのでしょうか? Engineerを示しているようにも見えますが、Engineerのworkは抽象メソッドです。この時点ではsuper.workは具体的な定義に束縛されていません。
トレイト内でsuperを使用した呼び出しを行った場合、メソッドの具体的な定義がしてあるクラスやトレイトの後でミックスインされるのであれば正しく動きます。このトレイトがミックスインされたときにsuper.workの定義内容が動的に束縛されます。
このような処理をやっていることをコンパイラに教えるために「abstract override」というキーワードが必要です。この修飾子はトレイトで記述することはできますが、クラスで記述することはできません。
では、ProgrammerトレイトをミックスインしてPersonクラスをインスタンス化してみましょう。
scala> val p = new Person with Programmer class Engineer constructor class Person constructor trait Programmer constructor p: Person with Programmer = $anon$1@4318dcec scala> p.work(60) Programmer#work start Person#work start 1つのタスクを45分で行います Person#work end Programmer#work end
workメソッドを呼ぶと、Programmerトレイトのworkメソッドが呼ばれ、その後にPersonクラスのworkが実行されていることが分かります。
Copyright © ITmedia, Inc. All Rights Reserved.