前述のEngineerクラスはコンストラクタを持っていませんでしたが、コンストラクタを持っていた場合、サブクラスから呼び出したい場合もあります。そのような場合、サブクラス定義時に呼び出したいスーパークラスのコンストラクタを指定できます。
ではEngineerクラスにnameを受け取るコンストラクタを追加して、サブクラスであるProgrammerクラスから呼び出してみましょう。
abstract class Engineer(val name:String) { println("Engineer.name=" + name) def work():Unit } class Programmer(name:String,val age:Int) extends Engineer(name){ println("Programmer.name=" + name) println("Programmer.age=" + age) def work() = printf("%s(%d)さんはコーディングします",name,age) }
サブクラスからスーパークラスのコンストラクタを呼び出すには、extendsでクラスを指定した後にコンストラクタの引数を渡します。ここで指定する引数は、スーパークラスの基本コンストラクタのものでも補助コンストラクタのものでも構いません。
Programmerクラスはコンストラクタで「name」「age」を受け取り、nameをスーパークラスのコンストラクタへ渡しています。実際にREPLでProgrammerクラスをインスタンス化してみます。
scala> val p = new Programmer("taro",30) Engineer.name=taro Engineer.age=30 Programmer.name=taro Programmer.age=30 p: Programmer = Programmer@2bbf1be2
最初にスーパークラスのコンストラクタが呼ばれ、その後にサブクラスのコンストラクタが実行されています。
サブクラスでメンバをオーバーライドさせたくないときには、「final」キーワードを使います。クラスのメンバに「final」を付ければオーバーライドを禁止できますし、クラスに「final」を付ければ継承自体を禁止できます。
//finalクラス final class X { def print() = println("final class") } class Y { //finalメソッド final def print() = println("final method") } //エラー class EX_X extends X class EX_Y extends Y { //エラー override def print() = println("override method") }
シールドクラスとは、サブクラスを作成するための制限を付けたクラスです。
シールドクラスを定義するには、クラス定義の先頭に「sealed」キーワードを付けます。sealedを付けられたクラスは、「同一ファイル内」のクラスからは継承できますが、別ファイル内で定義されたクラスからは継承できないという特徴を持ちます。
ただし、シールドクラスを継承したクラスは、別ファイルのクラスからも継承可能です。
シールドクラスはパターンマッチを記述するとき役に立ちます。ケースクラスを使用してパターンマッチを記述するとき、可能なケースを網羅しなければいけません。最後にデフォルト式を追加すれば漏れはなくなりますが、デフォルト処理がないときはすべてのケースが網羅されたという保証がなくなっていまいます。
シールドクラスを継承するケースクラスを使ってマッチ式を書くと、対応できていないパターンの組み合わせをチェックして警告してくれます。
例を見てみましょう。Engineerクラスをシールドクラスとして、それを継承するクラスを複数作成します(※これらは1つのソースファイルに記述してください)。
package sealedmodel sealed abstract class Engineer case class Programmer extends Engineer case class Tester extends Engineer case class Architect extends Engineer
ここで、わざとパターンマッチに漏れがある記述をしてみます。
import sealedmodel._ object Main { def main(args: Array[String]) = { val e:Engineer = new Programmer e match { case p:Programmer => println("Programmer") case t:Tester => println("Tester") } } }
Architectに対するマッチ式も、デフォルト式も記述していません。そのため、下記のような警告が出ます。
match is not exhaustive! missing combination Architect
このように、sealedを使えばケース漏れで実行時にMatchErrorが発生するのを予防できます。
しかし時には、この警告が煩わしいこともあります。例えば、あるケースではProgrammerかTesterしかあり得ないということが分かり切っているときなどです。
そういったときには通常、デフォルト式(case _)を追加すればよいのですが、わざわざ実行されないことが分かっているコードを記述するのも面倒です。
そんなときは@uncheckedアノテーションを使用しましょう。
(e: @unchecked) match { case p:Programmer => println("Programmer") case t:Tester => println("Tester") }
式の後ろに「:」を付けて、@とアノテーションの名前を記述します。パターンマッチのセレクタ式が、この@uncheckedアノテーションを持つ場合、その後に記述される各パターンに対してチェックを行いません。
今回はパッケージとインポートのいろいろな使用方法を紹介しました。また、アクセス修飾子の種類や、Scalaにおけるオブジェクトの継承、抽象クラスやオーバーライドの基本、finalクラス、sealedクラスの使い方も紹介しました。
全体的にJavaよりも多様な機能があり、より細かく制御できるようになっています。
次回はScalaの重要な機能の1つ、トレイトを紹介します。
中村修太(なかむら しゅうた)
クラスメソッド勤務の新しもの好きプログラマーです。昨年、東京から山口県に引っ越し、現在はノマドワーカーとして働いています。好きなJazzを聴きながらプログラミングするのが大好きです。
Copyright © ITmedia, Inc. All Rights Reserved.