Scalaの特徴を紹介し、基本構文や関数、クラスなど、Scalaの基本的な機能について解説する入門連載
前回の記事「Scalaのパッケージ、アクセス修飾子、オブジェクト継承」では、オブジェクト指向言語ではとても重要な機能であるクラスの継承と、Scalaのパッケージについて紹介しました。今回は、Scalaのオブジェクト指向を構成するもう1つの重要な機能、「トレイト」を紹介します。
なお、本記事ではオブジェクト指向自体やクラスやインターフェイス、継承の概念、それらに関連した基本的な事柄について詳細な説明は行いません。
オブジェクト指向の基礎やインターフェイスについて確認したい方は、以下の記事をご覧ください。
プログラムを「変更」しやすくする“インターフェイス”
【改訂版】Eclipseではじめるプログラミング(9) Javaにとって非常に重要な概念“インターフェイス”を理解すると、プログラムを交換可能な部品として再利用しやすくなります
「Java Solution」フォーラム 2009/7/23
いまさら聞けないJavaによるオブジェクト指向の常識
プログラマーの常識をJavaで身につける(11) Javaを学習する際に「オブジェクト指向」という言葉をよく耳にすると思いますが、いまさら聞けない人はぜひ読んでみてください
「Java Solution」フォーラム 2008/5/8
第1回記事では、Scala標準のREPLとScala IDEで動作を確認してみました。今後本記事のサンプルコードは、どちらで確認しても問題はありませんが、対話的に実行でき、1文ごとにコードの結果が分かって便利なので、基本的にはREPLを用いて説明していきます。
Scala IDEを使用する場合、第1回記事の『Scala IDE for Eclipseで「Hello Scala!」』を参照してプロジェクトを作成して実行してください。REPLを使用する場合は、コンソール上でscalaコマンドを実行し、REPLを起動してください。
Javaでは、2つ以上のクラスから継承(多重継承)を行うことはできませんが、1つのクラスに複数の「インターフェイス(interface)」の実装を持つことはできます。こうすることで疑似的に多重継承を実現し、いろいろな機能をクラスに持たせることができますが、Javaのインターフェイスはメソッドの宣言のみで実装を持てないため、用意するクラスにすべてのインターフェイスの実装を記述する必要があります。
このように、多くのインターフェイスのほとんどの機能は、実装するどのクラスでも、だいたい同じようなコードになることが多いですが、コードを組み込む仕組みはJavaにはないので、同じような実装を記述しなければならないのが現状です。
ScalaでもJavaと同じく多重継承は禁止されていますが、インターフェイスの代わりにトレイト(trait)を使用できます。トレイトはJavaのインターフェイスと同じように、複数のトレイトをミックスイン(mix-in)することが可能です。
ScalaのトレイトとJavaのインターフェイスの違いとしては、「トレイトは実装を持つことができる」ということです。このため、デフォルト実装を各トレイトに記述しておけば、ミックスインするクラスでそのまま使用できます。
トレイトは「trait」キーワードを使用すること以外、クラス定義とよく似ています。クラスと同じくアクセス修飾子も指定できますし、ほかのクラスを継承したり、ほかのトレイトをミックスインすることもできます。
<アクセス修飾子> trait トレイト名{ //フィールドやメソッドの定義 }
ここからは、トレイトを実際に使用してみましょう。まずは、プログラマを表現するトレイトを定義します。このトレイトはcodingメソッドを持ちます。そして、ProgrammerトレイトをミックスインするPersonクラスも定義します。このProgrammerトレイトとPersonクラスを定義してください。
trait Programmer { def coding = println("コーディングします") } class Person(val name:String) extends Programmer
クラスが明示的に継承を行わない場合、「extends」を使用してトレイトをミックスインします。もし、ほかのクラス/オブジェクトを継承する場合は、「with」キーワードを使用してトレイトをミックスインします。
class ChildClass extends ParentClass with TraitA with TraitB
上記のようにすれば、ChildClassはParentClassを継承し、TraitAとTraitBをミックスインしたことになります。
では、Personクラスをインスタンス化して使用してみましょう。Programmerトレイトをミックスインしているので、codingメソッドを使えます。
scala> val p = new Person("taro") p: Person = Person@48bc9f58 scala> p.coding コーディングします
また、ミックスインは定義時だけではなく、インスタンス化のタイミングで行うこともできます。デザイナ用トレイトを定義して、インスタンス化のときにミックスインしてみましょう。
trait Designer { def design = println("デザインします") }
newでインスタンス化し、そのまま「with」キーワードを付与してトレイトをミックスインしています。
scala> val p = new Person("taro") with Design p: Person with Designer = $anon$1@7e61daf0 scala> p.coding コーディングします scala> p.design デザインします
事前にミックスインしたのと同じように、ミックスインしたトレイトのメソッドを使用できますね。
トレイトは、メソッドだけではなくフィールドも持てます。さらに、本体にコードを書けばコンストラクタ処理もできます。しかし、基本コンストラクタに渡すためのパラメータは取れず、補助コンストラクタの定義ができない点に注意してください。
trait ProjectManager { val budget:Int = 10000000 println("budget=" + budget) def manage = println("プロジェクト管理します") }
Personインスタンス生成時にProjectManagerトレイトをミックスインしてみましょう。
scala> val p = new Person("taro") with ProjectManager budget=10000000 p: Person with ProjectManager = $anon$1@46380f83
インスタンス化したタイミングでトレイトのコンストラクタが実行されています。
複数のトレイトを使用した場合、コンストラクタが実行される順番はどうなっているでしょうか。いくつかサンプルを定義して試してみましょう。
class Parent { println("Parent") } class Child extends Parent { println("Child") } trait A { println("trait A") } trait B { println("trait B") } trait C { println("trait C") }
REPLでクラスをインスタンス化してコンストラクタの実行順序を確認してみましょう。
scala> val c = new Child with A with B with C Parent Child trait A trait B trait C c: Child with A with B with C = $anon$1@4349c220
スーパークラスのコンストラクタ、自分自身のコンストラクタが呼ばれ、その後ミックスインしている一番左のトレイトのコンストラクタから順番に呼ばれているのが分かります。
Copyright © ITmedia, Inc. All Rights Reserved.