7 トレイト

スーパークラスからコードを継承する以外にも、Scalaでは1つあるいは複数の トレイト からコードをインポートできます。

Javaプログラマがトレイトとは何かを理解する一番簡単な方法は、コードを含むことも可能なインターフェイスとみなすことでしょう。Scalaでは、あるクラスがトレイトから継承した場合、そのトレイトのインターフェイスを実装し、そのトレイトに含まれるコードも全て継承します。

トレイトの有用性を見るために、古典的な例である、順序付きオブジェクトを見てみましょう。あるクラスのオブジェクト同士を比較できると、例えばソートしたりする場合など、しばしば役に立ちます。Javaでは、比較可能なオブジェクトは Comparable インターフェイスを実装します。Scalaでは、Comparable と同等品の、Ordと呼ぶことにするトレイトを定義することで、Javaよりもう少しうまくやれます。

オブジェクトを比較するには、6つの異なる述語、小さい・小さいか等しい・等しい・等しくない・大きいか等しい・大きい、があると便利です。しかしそれら全てを定義するのは冗長に過ぎます。なぜなら6つのうちの2つを使って残りの4つは表せるのですから。例えば、等しいと小さいの2つの述語があれば、他を表すことができます。Scalaでは、それらは次のようなトレイト宣言でうまく表せます。

trait Ord { def < (that: Any): Boolean def <=(that: Any): Boolean = (this < that) || (this == that) def > (that: Any): Boolean = !(this <= that) def >=(that: Any): Boolean = !(this < that) }

この定義によって、Java の Comparable インターフェイスと同じ役割をする Ord と呼ばれる型を作ると共に、抽象的な述語1つを使って3つの述語のデフォルト実装が行われます。等しい・等しくないという述語は、全てのオブジェクトにデフォルトで存在するため、ここには現れません。

上の例で 使われている Any 型は Scala の全ての型のスーパータイプの型です。Javaの Object 型より更に一般的といえます。なぜなら、Int や Float といった基本型のスーパータイプでもあるからです。

従って、比較可能なクラスのオブジェクトを作るためには、等しいことと小さいことを判定する述語を定義し、上記 Ord トレイトをミックスインすれば充分です。例として、グレゴリオ暦の日付を表す Date クラスを定義してみましょう。そのような日付は、全て整数値である日・月・年から成ります。従って Date クラスの定義は次のようになります。

class Date(y: int, m: Int, d: Int) extends Ord { def year = y def month = m def day = d override def toString(): String = year + "" + month + "" + day

ここで重要なことは、クラス名とパラメータに続く extends Ord 宣言です。これは Date クラスが Ord トレイトを継承することを宣言しています。

そして、Object から継承している equals メソッドを再定義し、個々のフィールドを比較することで正しく日付を比較するようにします。equals の Java でのデフォルト実装は、オブジェクトを物理的に比較するため使えません。下記のような定義になります。

override def equals(that: Any): Boolean = that.isInstanceOf[Date] && { val o = that.asInstanceOf[Date] o.day == day && o.month == month && o.year == year }

このメソッドではあらかじめ定義された、isInstanceOf と asInstanceOf というメソッドを使用しています。最初の isInstanceOf は、Java の instanceof 演算子に対応しており、適用されたオブジェクトが与えられた型のインスタンスである場合にのみ真を返します。2つめの asInstanceOf は、Java のキャスト演算子に対応します。もしオブジェクトが与えられた型のインスタンスならばそのように観られるようになり、そうでなければ ClasscastException が投げられます。

最後に下記のように、小さいことを判定する述語を定義します。別のあらかじめ定義されたメソッドである error を使います。error は与えられたエラーメッセージ付きの例外を投げるメソッドです。

def <(that: Any): Boolean = { if (!that.isInstanceOf[Date]) error("cannot compare " + that + " and a Date") val o = that.asInstanceOf[Date] (year < o.year) || (year == o.year && (month < o.month || (month == o.month && day < o.day))) }

これで Date クラスの定義が完了しました。このクラスのインスタンスは日付であり比較可能なオブジェクトでもあります。更に、上で述べた6つの比較用の述語が定義されています。equalsと < は Date クラスの定義の中に直接現れており、他は Ord トレイトから継承しています。

トレイトがここで示したよりも有用な状況があるのは勿論ですが、そういった例を長々と議論するのはこの文書の目的から外れます。