case class
コップ本曰く
- 通常のクラスと似ているいくつか重要な違いをもつ、不変データをモデリングするのに適したクラス
- 紋切り型コードをあまり使わずオブジェクトのパターンマッチを行えるようにするScalaの記法(ボイラープレートが減る。パターンマッチのためにunapplyをがんばって記述しなくていいってことかな‥?)
宣言すると…
- 便利なメソッドが定義される
- コンパニオンオブジェクトが定義される
- 基本コンストラクタ引数全てが「val」で宣言されたフィールドになる
- 明示的に「var」のフィールドにできる
- 基本は「val」になるっているから
不変データをモデリングするのに適したクラス
として位置づけているんだろうなと考えられそう
scala> case class Person(name: String, age: Int)
// name: String
// age: Int
// がプロパティになる
便利なメソッド
- copy
- 一部(全部でも)のフィールドを変更した新しいインスタンスを生成する
scala> val oldTaro = taro.copy(age = 40)
scala> println(oldTaro.age)
40
scala> println(taro.age)
20
- toString
- いい感じに文字列で表現してくれる
scala> val taro = Person("taro", 20)
scala> println(taro.toString())
Person(taro,20)
- equals
-
==
はequals
が定義されていると処理がequals
に移譲される
-
// 同一のインスタンスをさしていたら"true"が返る
scala> val taro2 = taro
taro2: Person = Person(taro,20)
scala> taro2 == taro
res6: Boolean = true
// 別のインスタンス、別の値を持っていたら"false"が返る
scala> val ziro = Person("ziro", 20)
ziro: Person = Person(ziro,20)
scala> ziro == taro
res7: Boolean = false
// 別インスタンス、同一で値だったら"true"が返る
scala> val cloneTaro = Person("taro", 20)
cloneTaro: Person = Person(taro,20)
コンパニオンオブジェクト
- apply
- new がなくてもインスタンス生成できる
- unapply
- コンストラクタパターンを使ったパターンマッチができる
toString
- いい感じに文字列で表現してくれる
- いい感じってどういうことだ?
- scalaコンソールを
scala -Xprint:typer
で呼び出して、ASTをのぞいて見る -
def toString(): String = scala.runtime.ScalaRunTime._toString(Person.this);
-
scala.runtime.ScalaRunTime
に答えはあるようだ
-
- toStringを追うために必要な箇所だけ抜粋
case class Person extends AnyRef with Product with Serializable
override <synthetic> def productIterator: Iterator[Any] = scala.runtime.ScalaRunTime.typedProductIterator[Any](Person.this);
override <synthetic> def productPrefix: String = "Person";
<synthetic> def productArity: Int = 2;
<synthetic> def productElement(x$1: Int): Any = x$1 match {
case 0 => Person.this.name
case 1 => Person.this.age
case _ => throw new IndexOutOfBoundsException(x$1.toString())
};
override <synthetic> def toString(): String = scala.runtime.ScalaRunTime._toString(Person.this);
scala.runtime.ScalaRunTime
scala.runtime.ScalaRunTime
def _toString(x: Product): String = x.productIterator.mkString(x.productPrefix + "(", ",", ")")
-
scala.runtime.ScalaRunTime
ではProduct
を実装したオブジェクトを取り、productIterator
の要素をString
にして結合しているようだ-
Product
を実装する要素にproductIterator
とproductPrefix
があるようだがcase classでは二つともoverride
している -
productPrefix
はcase classならクラス名になっていたね - 渡されたオブジェクトがcase classなら
productIterator
が一つ一つのフィールドを文字列にしているようだ - case classの
productIterator
ではcase class Personの定義を見ると、typedProductIterator
に処理を委譲している- 委譲先では
productElement
をproductArity
の数だけ呼び出すことですべてのフィールドを返している
- 委譲先では
-
scala.runtime.ScalaRunTime
/** A helper for case classes. */
def typedProductIterator[T](x: Product): Iterator[T] = {
new AbstractIterator[T] {
private var c: Int = 0
private val cmax = x.productArity
def hasNext = c < cmax
def next() = {
val result = x.productElement(c)
c += 1
result.asInstanceOf[T]
}
}
}
- case class のtoStringはProductの実装から来ているんだね
- とくにcase classでは
productPrefix
では名前を、productElement
でフィールドを全て返すように実装しているからPerson("taro", 22)
となるんだな。納得
- とくにcase classでは
どうだっていいかもしれないこと
- case class の
case
ってネーミングどこから来ているんですかね?