今日はオブジェクトの章を。
オブジェクト
概要
ポイントっぽいところをまとめると以下。
- Scalaでは全ての値がオブジェクト
- 全てのメソッドは何らかのオブジェクトに属している
object
キーワードによって、同名のシングルトンオブジェクトを現在の名前空間の下に1つ定義可能- シングルトンオブジェクトにはそのオブジェクト固有のメソッド、フィールドを定義可能
- 単純に上記の3つを読む限りだと、C++の
namespace
っぽい扱いに感じる。
- 単純に上記の3つを読む限りだと、C++の
構文はclass
と似ていて以下の通り。
object <オブジェクト名> extends <クラス名> (with <トレイト名>)* { (<フィールド定義> | <メソッド定義>)* }
クラスを継承できる?ようになってる。。。with <トレイト名>
でmix-inが可能と書いてある(traitについては次の記事で)。用途は「オブジェクト名を既存のクラスのサブクラス等として振る舞わせる」ようなケースがあるらしい。
一例として挙げられていたのは以下。
Scalaの標準ライブラリでは、
Nil
というobject
がありますが、これはList
の一種として振る舞わせたいため、List
を継承しています。
用途
object
構文の主な用途としては以下が紹介されている。
- ユーティリティメソッドやグローバルな状態の置き場所(Javaで言うstaticメソッドやフィールド)
- Scalaの
class
はstaticメソッドやフィールドを持てない。
- Scalaの
- 同名クラスのオブジェクトのファクトリメソッド
2番目の例は、前回のclass
の記事でも使ったPoint
クラスを使って説明されている。
class Point(_x: Int, _y: Int) { val x = _x val y = _y } object Point { def apply(x: Int, y: Int): Point = new Point(x, y) } object ClassPoint { def main(args: Array[String]): Unit = { val point = Point(100, 2) // pattern 4 println(point.x, point.y) }
上記のようにmainではPoint(100, 2)
のようにnew
なしでPointクラスを生成している。このケースではobject Point
にapply
メソッドを定義したことにより、Point(100, 2)
がPoint.apply(100, 2)
と解釈されるようになっている。
これにより以下のメリットが得られる。
- クラスの実装詳細を内部に隠蔽出来る
- Pointではなく、そのサブクラスのインスタンスを返すことが出来る
このあとにケースクラスについての言及があるが、ここでは割愛して次に進む(後で使い方含めて説明があるらしいので)。
コンパニオンクラス
クラスと同じファイル内、同じ名前で定義されたシングルトンオブジェクトは、コンパニオンオブジェクトと呼ばれ、対応するクラスに対して特権的なアクセス権をもっているそうだ。
早速、例を見ていく。
NGになるケース
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) } } object ClassCompanionNG { def main(args: Array[String]): Unit = { Hoge.printWeight() } }
上記では、class Person
内のprivate
なフィールドであるweight
を表示するメソッドprintWeight
を持つobject
であるHoge
が定義されており、それをmainで呼び出している。
実行すると結果は以下のようにエラーになる。
$ scala 20180915_02_object_companion_ng.scala /home/dnn-admin/workspace/hw/study/2000_chisel/200_examples/20180915_02_object_companion_ng.scala:6: error: value weight in class Person cannot be accessed in Person println(taro.weight) ^ one error found
OKになるケース
次はOKになる場合だ。
先ほどのNGケースとの違いはobject Hoge
がobject Person
に変更されているだけになる。
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) } } object ClassCompanionOK { def main(args: Array[String]): Unit = { Person.printWeight() } }
では実行してみよう。
$ scala 20180915_03_object_companion_ok.scala
70
今度は正常に実行されて、printWeight
メソッド内で生成されたPerson
クラスのオブジェクトのweight
フィールドの値が表示された。
なおコンパニオンオブジェクトであってもprivate[this]
で定義したフィールドにはアクセスが出来ないとのこと。
一応こちらも確認してみる。
class Person(name: String, age: Int, private[this] val weight: Int) { def print() = { println(this.weight) } } object Person { def printWeight(): Unit = { val taro = new Person("Taro", 20, 70) println(taro.weight) //taro.print() } } object ClassCompanionOK { def main(args: Array[String]): Unit = { Person.printWeight() } }
先ほど正常に実行できたソースに対してprivate val weight
→private[this] val weight
の修正を入れて、同様にprintWeight()
メソッドを呼ぶと以下のようになる。
$ scala 20180915_04_object_companion_ng_private_this.scala
/home/dnn-admin/workspace/hw/study/2000_chisel/200_examples/20180915_04_object_companion_ng_private_this.scala:6: error: value weight is not a member of Person
println(taro.weight)
^
one error found
一方で、上記でコメントアウトしたtaro.print()
を呼ぶようにすると、正常に実行が可能となりクラス内部からのアクセスが可能なことが確認できた。
$ scala 20180915_04_object_companion_ng_private_this.scala
70
さて練習問題を、、、と思ったら本章の練習問題は以下の通りで、今確かめたことだった。。。
クラスを定義して、そのクラスのコンパニオンオブジェクトを定義してみましょう。コンパニオンオブジェクトが同名のクラスに対する特権的なアクセス権を持っていることを、クラスのフィールドを
private
にして、そのフィールドへアクセスできることを通じて確認してみましょう。また、クラスのフィールドをprivate[this]
にして、そのフィールドへアクセスできないことを確認してみましょう。
というわけでオブジェクトの章はおしまい。