ハードウェアの気になるあれこれ

技術的に興味のあることを調べて書いてくブログ。主にハードウェアがネタ。

Scalaの勉強 - オブジェクト

スポンサーリンク

今日はオブジェクトの章を。

オブジェクト

概要

ポイントっぽいところをまとめると以下。

  • Scalaでは全ての値がオブジェクト
    • これはpythonと一緒の思想。pythonの場合は全ての値がobjectクラスを継承している
    • 一方でScalaの場合はobjectという構文でそれを実現している(ように見えた)
  • 全てのメソッドは何らかのオブジェクトに属している
  • objectキーワードによって、同名のシングルトンオブジェクトを現在の名前空間の下に1つ定義可能
  • シングルトンオブジェクトにはそのオブジェクト固有のメソッド、フィールドを定義可能
    • 単純に上記の3つを読む限りだと、C++namespaceっぽい扱いに感じる。

構文はclassと似ていて以下の通り。

object <オブジェクト名> extends <クラス名> (with <トレイト名>)* {
  (<フィールド定義> | <メソッド定義>)*
}

クラスを継承できる?ようになってる。。。with <トレイト名>でmix-inが可能と書いてある(traitについては次の記事で)。用途は「オブジェクト名を既存のクラスのサブクラス等として振る舞わせる」ようなケースがあるらしい。

一例として挙げられていたのは以下。

Scalaの標準ライブラリでは、 Nil という object がありますが、これは List の一種として振る舞わせたいため、 List を継承しています。

用途

object構文の主な用途としては以下が紹介されている。

  • ユーティリティメソッドやグローバルな状態の置き場所(Javaで言うstaticメソッドやフィールド)
    • Scalaclassはstaticメソッドやフィールドを持てない。
  • 同名クラスのオブジェクトのファクトリメソッド

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 Pointapplyメソッドを定義したことにより、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 Hogeobject 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 weightprivate[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]にして、そのフィールドへアクセスできないことを確認してみましょう。

というわけでオブジェクトの章はおしまい。