Scalaのケースクラス

ケースクラス

ケースクラスとしてクラスを定義すると、以下の恩恵があります。

  • フィールドが公開される(全コンストラクタ引数にvalを付けてくれる)
  • toStringなど便利なメソッドが実装される
  • コンパニオンオブジェクトが自動生成される
  • コンパニオンオブジェクトにnewの代わりになるapplyメソッドが実装される

ケースクラスを使わずに、ケースクラスの一部を実現してみると、以下になります。

class Robot(val name: String) {
  override def toString = s"Robot($name)"
}

object Robot {
  def apply(name: String): Robot = new Robot(name)
}

val robota = Robot("ロボ太")

scala> robota.name
res0: String = ロボ太

scala> robota.toString
res1: String = Robot(ロボ太)

実際にはさらに多くの記述が追加されていますが、ケースクラスではそれらを一行で実現できます。

case class Robot(name: String)

val robota = Robot("ロボ太")

scala> robota.name
res0: String = ロボ太

scala> robota.toString
res1: String = Robot(ロボ太)

クラスとケースクラスの同値性

通常のクラスでは以下になります。
すなわち「インスタンスが違えばフィールドが同じでも==の結果はfalse」です。

class Robot(val name: String)

val r1 = new Robot("ロボ太")
val r2 = new Robot("ロボ太")
val r3 = new Robot("ロボ子")

scala> r1 == r1
res0: Boolean = true

scala> r1 == r2
res1: Boolean = false

scala> r1 == r3
res2: Boolean = false

ケースクラスでは以下になります。
すなわち「インスタンスが違っていてもフィールドが同じであれば==の結果はtrue」です。

case class caseRobot(name: String)

val cr1 = caseRobot("ロボ太")
val cr2 = caseRobot("ロボ太")
val cr3 = caseRobot("ロボ子")

scala> cr1 == cr1
res3: Boolean = true

scala> cr1 == cr2
res4: Boolean = true

scala> cr1 == cr3
res5: Boolean = false

ケースクラスのメソッド

ケースクラスで自動的に実装されるメソッドです。

toString

クラス名とフィールドの値を返します。(普通のクラスのtoStringは、クラス名のみ返します。)

class Robot(name: String)
case class Human(name: String)

val r1 = new Robot("ロボ太")
val h1 = new Human("太郎")

scala> r1.toString
res0: String = Robot@4417fd8a

scala> h1.toString
res1: String = Human(太郎)

copy

元のインスタンスのフィールド値を元に、新たなインスタンスを生成します。
ケースクラスのフィールドはval(定数)ですから、フィールド値を変更するときに使えます。

case class Robot(name: String, id: Int)

scala> val r1 = Robot("ロボ太", 1)
r1: Robot = Robot(ロボ太,1)

scala> val r2 = r1.copy("ロボ子", 2)
r2: Robot = Robot(ロボ子,2)

scala> val r3 = r2.copy(id = 3)
r3: Robot = Robot(ロボ子,3)

equals

フィールドが同じであればtrueを返します。もちろんクラスが違えばfalseです。

case class Robot(name: String, id: Int)
case class Human(name: String, id: Int)

val r1 = Robot("ロボ太", 1)
val r2 = Robot("ロボ太", 1)
val r3 = Robot("ロボ太", 3)
val h1 = Human("ロボ太", 1)

scala> r1.equals(r2)
res0: Boolean = true

scala> r1.equals(r3)
res1: Boolean = false

scala> r1.equals(h1)
res2: Boolean = false

canEqual

元となったクラスが同じであればtrueを返します。

case class Robot(name: String, id: Int)
case class Human(name: String, id: Int)

val r1 = Robot("ロボ太", 1)
val h1 = Human("ロボ太", 1)

scala> r1.canEqual(r3)
res3: Boolean = true

scala> r1.canEqual(h1)
res4: Boolean = false

ケースオブジェクト

クラスと同じように、ケースオブジェクトを定義できます。

case object MyObj {
  // なにか
}

ただし、オブジェクトの特徴のため以下のようになります。toStringメソッドはクラスと変わりません。

  • オブジェクトはコンストラクタ引数をとれませんから、case object MyObj(name: Sring)のようにフィールドを生成することはできません。
  • オブジェクトはシングルトンですから、copyメソッドは実装されません。またequals・canEqualメソッドで等しくなるのは自分自身のみです。
  • オブジェクトですから、コンパニオンオブジェクトやその中に定義されるはずのapply・unapplyメソッドは実装されません。