LoginSignup
41
19

More than 5 years have passed since last update.

case class‥ってなんだ?

Last updated at Posted at 2017-09-23

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を実装する要素にproductIteratorproductPrefixがあるようだがcase classでは二つともoverrideしている
    • productPrefixはcase classならクラス名になっていたね
    • 渡されたオブジェクトがcase classならproductIteratorが一つ一つのフィールドを文字列にしているようだ
    • case classのproductIteratorではcase class Personの定義を見ると、typedProductIteratorに処理を委譲している
      • 委譲先ではproductElementproductArityの数だけ呼び出すことですべてのフィールドを返している
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ってネーミングどこから来ているんですかね?
41
19
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
41
19