オブジェクト
Scalaでは、全ての値がオブジェクトです。また、全てのメソッドは何らかのオブジェクトに所属しています。そのため、Javaのようにクラスに属するstaticフィールドやstaticメソッドといったものを作成することができません。その代わりに、object
キーワードによって、同じ名前のシングルトンオブジェクトを現在の名前空間の下に1つ定義することができます。object
キーワードによって定義したシングルトンオブジェクトには、そのオブジェクト固有のメソッドやフィールドを定義することができます。
object
構文の主な用途としては、
- ユーティリティメソッドやグローバルな状態の置き場所(Javaで言うstaticメソッドやフィールド)
- オブジェクトのファクトリメソッド
- Singletonパターン
3つが挙げられます。とはいえ、Singletonパターンを実現するために使われることはほとんどなく、もっぱら最初の 2つの用途で使われます。
objectの基本構文はクラスとほとんど同じで、
object オブジェクト名 extends クラス名 with トレイト名1 with トレイト名2 ... {
本体
}
のようになります。Scalaでは標準でPredef
というobject
が定義・インポートされており、これは最初の使い方に当てはまります。たとえば、println()などとなにげなく使っていたメソッドも実はPredef objectのメソッドなのです。
一方、2番めの使い方について考えてみます。点を表すPointクラスのファクトリを
object
で作ろうとすると、次のようになります。applyという名前のメソッドはScala処理系によって特別に扱われ、Point(x)
のような記述があった場合で、Point object
にapply
という名前のメソッドが定義されていた場合、Point.apply(x)
と解釈されます。これを利用してPoint objectの
apply
メソッドでオブジェクトを生成するようにすることで、Point(3, 5)
のような記述でオブジェクトを生成できるようになります。
scala> class Point(val x:Int, val y:Int)
defined class Point
scala> object Point {
| def apply(x: Int, y: Int): Point = new Point(x, y)
| }
defined object Point
warning: previously defined class Point is not a companion to object Point.
Companions must be defined together; you may wish to use :paste mode for this.
これは、new Point()で直接Pointオブジェクトを生成するのに比べて、
- クラス(Point)の実装詳細を内部に隠しておける(インタフェースのみを外部に公開する)
- Pointではなく、そのサブクラスのインスタンスを返すことができる
といったメリットがあります。なお、上記の記述はケースクラスを用いてもっと簡単に
scala> case class Point(x: Int, y: Int)
defined class Point
と書けます。ケースクラスは後述するパターンマッチのところでも出てきますが、ここではその使い方については触れません。簡単に言うとケースクラスは、それをつけたクラスのプライマリコンストラクタ全てのフィールドを公開し、equals()
・hashCode()
・toString()
などのオブジェクトの基本的なメソッドをオーバーライドしたクラスを生成し、また、そのクラスのインスタンスを生成するためのファクトリメソッドを生成するものです。たとえば、
case class Point(x: Int, y: Int)
で定義した Point
クラスは equals()
メソッドを明示的に定義してはいませんが、
Point(1, 2).equals(Point(1, 2))
を評価した値はtrue
になります。
コンパニオンオブジェクト
クラスと同じファイル内、同じ名前で定義されたシングルトンオブジェクトは、コンパニオンオブジェクトと呼ばれます。コンパニオンオブジェクトは対応するクラスに対して特権的なアクセス権を持っています。たとえば、
weightをprivate
にした場合、
class Person(name: String, age: Int, private val weight: Int)
object Hoge {
def printWeight(): Unit = {
val taro = new Person("Taro", 20, 70)
println(taro.weight)
}
}
はNGですが、
class Person(name: String, age: Int, private val weight: Int)
object Person {
def printWeight(): Unit = {
val taro = new Person("Taro", 20, 70)
println(taro.weight)
}
}
はOKです。なお、コンパニオンオブジェクトでも、private[this]
(そのオブジェクト内からのみアクセス可能)なクラスのメンバーに対してはアクセスできません。単にprivate
とした場合、コンパニオンオブジェクトからアクセスできるようになります。
上記のような、コンパニオンオブジェクトを使ったコードをREPLで試す場合は、REPLの:paste
コマンドを使って、クラスとコンパニオンオブジェクトを一緒にペーストするようにしてください。クラスとコンパニオンオブジェクトは同一ファイル中に置かれていなければならないのですが、REPLで両者を別々に入力した場合、コンパニオン関係をREPLが正しく認識できないのです。
scala> :paste
// Entering paste mode (ctrl-D to finish)
class Person(name: String, age: Int, private val weight: Int)
object Person {
def printWeight(): Unit = {
val taro = new Person("Taro", 20, 70)
println(taro.weight)
}
}
// Exiting paste mode, now interpreting.
defined class Person
defined object Person
練習問題
クラスを定義して、そのクラスのコンパニオンオブジェクトを定義してみましょう。コンパニオンオブジェクトが同名のクラスに対する特権的なアクセス権を持っていることを、クラスのフィールドをprivate
にして、そのフィールドへアクセスできることを通じて確認してみましょう。また、クラスのフィールドをprivate[this]
にして、そのフィールドへアクセスできないことを確認してみましょう。