コンパニオンとstatic
コンパニオンとは、同一スコープ(同じソースファイル内や同じブロック内)で定義されているクラスとオブジェクトの関係を表す言葉です。以下のリスト7の例では、Bookmarkクラスはコンパニオンクラス、Bookmarkオブジェクトはコンパニオンオブジェクトと呼ばれます。
// Bookmarkコンパニオンクラス
// コンストラクタはprivateに変更
class Bookmark private ( val title:String, val url:URL, val date:Date )
// Bookmarkコンパニオンオブジェクト
object Bookmark {
// Bookmarkクラスを生成する
def apply( title:String, url:URL, date:Date ):Bookmark =
new Bookmark( title, url , date )
// 文字列のurlからBookmarkクラスを生成する
def apply( title:String, url:String, date:Date ):Bookmark =
new Bookmark( title, new URL( url ), date )
// dateを省略し、文字列のurlからBookmarkクラスを生成する
def apply( title:String, url:String ):Bookmark =
new Bookmark( title,new URL( url ),new Date )
}
上記のBookmarkクラスは、リスト4で定義したものを修正したバージョンです。基本コンストラクタにprivateキーワードが付与されているため、通常の手段ではこのBookmarkクラスをインスタンス化できません。
そこで、コンパニオン関係にあるBookmarkオブジェクトを利用します。コンパニオン関係にあるクラスとオブジェクトの間では、privateなメンバーへの特権的なアクセスが可能になります。これを利用して、BookmarkオブジェクトのapplyメソッドでBookmarkクラスのprivateな基本コンストラクタを呼び出してBookmarkクラスをインスタンス化しているのです。
Bookmarkオブジェクトには、リスト4の時点で補助コンストラクタとして定義されていたものもapplyメソッドをオーバーライドすることで実装しています。このようにすると、BookmarkクラスはBookmarkオブジェクトが持つファクトリーメソッド経由でのみインスタンスを生成することが可能となり、インスタンス生成をより細やかに制御することが可能です(例えば、キャッシュを行ったり事前条件のチェックをしたり)。
さて、applyというメソッド名ですが、これはScalaでは特別扱いされるメソッド名で、オブジェクトに対してあたかも関数呼び出しのようにしてメソッドを呼び出すことを可能とします。
Bookmark("hoge", "http://example.com")のように呼び出すと、applyメソッドが呼ばれます。これを利用して、シンタックスシュガーのようにBookmarkクラスをインスタンス化させているわけです。
では、REPLで動作確認をしてみましょう。REPLでは、入力行ごとにスコープが分割されるので、リスト7のコードをそのままREPLに入力してもエラーになってしまいます。コンパニオンをREPLで動作させるためには、以下のリスト8のように適当なobjectのインナークラス/オブジェクトとして定義することでREPL上でコンパニオン関係を作ることができます。
scala> object m {
| class Bookmark private ( val title:String, val url:URL, val date:Date )
|
| // Bookmarkコンパニオンオブジェクト
| object Bookmark {
| // Bookmarkクラスを生成する
| def apply( title:String, url:URL, date:Date ):Bookmark =
| new Bookmark( title, url , new Date )
... 中略
| }
defined module m
scala> val bookmark1 = m.Bookmark( "今から始めるScala 前編" ,"http://codezine.jp/article/detail/5193/")
bookmark1: m.Bookmark = m$Bookmark@5e681e45
scala> bookmark1.title
res3: String = 今から始めるScala 前編
scala> bookmark1.date
res4: java.util.Date = Fri Jun 18 15:38:58 JST 2010
コンパニオンオブジェクトは特権的なアクセス権を持つので、staticメソッドのようなインスタンスに束縛されない処理を定義するのに最適です。逆に、Scalaではstaticなメンバーをクラスに定義できません。
また、applyでコンパニオン関係のクラスのファクトリーメソッドを提供するような設計を、Scalaではよく行います。
