Scala Language
ケースクラス
サーチ…
構文
- caseクラスFoo()//パラメータを持たないcaseクラスは空のリストを持たなければなりません
- caseクラスFoo(a1:A1、...、aN:AN)//フィールドa1 ... aNを持つcaseクラスを作成する
- caseオブジェクトBar //シングルトンのcaseクラスを作成する
ケースクラスの等式
ケースクラスごとに無料で提供される機能の1つは、オブジェクトの参照の等価性をチェックするのではなく、個々のメンバーフィールドの値の等価性をチェックする自動生成equals
メソッドです。
普通のクラスでは:
class Foo(val i: Int)
val a = new Foo(3)
val b = new Foo(3)
println(a == b)// "false" because they are different objects
ケースクラスの場合:
case class Foo(i: Int)
val a = Foo(3)
val b = Foo(3)
println(a == b)// "true" because their members have the same value
生成されたコード成果物
case
修飾子を使用すると、Scalaコンパイラはクラスの共通定型コードを自動的に生成します。手動でこのコードを実装するのは退屈で、エラーの原因になります。次のケースクラス定義:
case class Person(name: String, age: Int)
...次のコードが自動的に生成されます:
class Person(val name: String, val age: Int)
extends Product with Serializable
{
def copy(name: String = this.name, age: Int = this.age): Person =
new Person(name, age)
def productArity: Int = 2
def productElement(i: Int): Any = i match {
case 0 => name
case 1 => age
case _ => throw new IndexOutOfBoundsException(i.toString)
}
def productIterator: Iterator[Any] =
scala.runtime.ScalaRunTime.typedProductIterator(this)
def productPrefix: String = "Person"
def canEqual(obj: Any): Boolean = obj.isInstanceOf[Person]
override def hashCode(): Int = scala.runtime.ScalaRunTime._hashCode(this)
override def equals(obj: Any): Boolean = this.eq(obj) || obj match {
case that: Person => this.name == that.name && this.age == that.age
case _ => false
}
override def toString: String =
scala.runtime.ScalaRunTime._toString(this)
}
case
修飾子は、コンパニオンオブジェクトも生成します。
object Person extends AbstractFunction2[String, Int, Person] with Serializable {
def apply(name: String, age: Int): Person = new Person(name, age)
def unapply(p: Person): Option[(String, Int)] =
if(p == null) None else Some((p.name, p.age))
}
object
に適用すると、 case
修飾子は(それほど劇的ではありませんが)同様の効果を持ちます。ここでの主な利点は、 toString
実装と、プロセス間で整合性のあるhashCode
値です。大文字と小文字のオブジェクトは、参照の等号を正しく使用することに注意してください。
object Foo extends Product with Serializable {
def productArity: Int = 0
def productIterator: Iterator[Any] =
scala.runtime.ScalaRunTime.typedProductIterator(this)
def productElement(i: Int): Any =
throw new IndexOutOfBoundsException(i.toString)
def productPrefix: String = "Foo"
def canEqual(obj: Any): Boolean = obj.isInstanceOf[this.type]
override def hashCode(): Int = 70822 // "Foo".hashCode()
override def toString: String = "Foo"
}
クラス自体とそのコンパニオンオブジェクトの両方でcase
修飾子によって提供されるメソッドを手動で実装することは、依然として可能です。
ケースクラスの基礎
通常のクラスと比較して、ケースクラスの表記にはいくつかの利点があります。
すべてのコンストラクタ引数は
public
あり、初期化されたオブジェクトでアクセスできます(通常、ここで説明したようにそうではありません)。case class Dog1(age: Int) val x = Dog1(18) println(x.age) // 18 (success!) class Dog2(age: Int) val x = new Dog2(18) println(x.age) // Error: "value age is not a member of Dog2"
toString
、equals
、hashCode
(プロパティに基づく)、copy
、apply
、unapply
の各メソッドの実装を提供します。case class Dog(age: Int) val d1 = Dog(10) val d2 = d1.copy(age = 15)
パターンマッチングのための便利なメカニズムを提供します:
sealed trait Animal // `sealed` modifier allows inheritance within current build-unit only case class Dog(age: Int) extends Animal case class Cat(owner: String) extends Animal val x: Animal = Dog(18) x match { case Dog(x) => println(s"It's a $x years old dog.") case Cat(x) => println(s"This cat belongs to $x.") }
ケースクラスと不動産
Scalaコンパイラは、デフォルトでパラメータリストのすべての引数の前にval
ます。つまり、デフォルトでは、ケースクラスは不変です。各パラメータにはアクセサメソッドがありますが、ミューテータメソッドはありません。例えば:
case class Foo(i: Int)
val fooInstance = Foo(1)
val j = fooInstance.i // get
fooInstance.i = 2 // compile-time exception (mutation: reassignment to val)
caseクラスのパラメータをvar
として宣言すると、デフォルトの動作がオーバーライドされ、caseクラスが変更可能になります。
case class Bar(var i: Int)
val barInstance = Bar(1)
val j = barInstance.i // get
barInstance.i = 2 // set
caseクラスが 'mutable'である場合の別の例は、caseクラスの値が可変である場合です。
import scala.collection._
case class Bar(m: mutable.Map[Int, Int])
val barInstance = Bar(mutable.Map(1 -> 2))
barInstance.m.update(1, 3) // mutate m
barInstance // Bar(Map(1 -> 3)
ここで起こっている「突然変異」は、 m
指すマップ内にあり、 m
自身ではないことに注意してください。したがって、ある他のオブジェクトがメンバとしてm
を持っていれば、変更も同様に見えます。次の例では、 instanceA
を変更するとinstanceA
も変更されinstanceB
。
import scala.collection.mutable
case class Bar(m: mutable.Map[Int, Int])
val m = mutable.Map(1 ->2)
val barInstanceA = Bar(m)
val barInstanceB = Bar(m)
barInstanceA.m.update(1,3)
barInstanceA // Bar = Bar(Map(1 -> 3))
barInstanceB // Bar = Bar(Map(1 -> 3))
m // scala.collection.mutable.Map[Int,Int] = Map(1 -> 3)
特定の変更を加えたオブジェクトのコピーを作成する
ケースクラスは、特定の変更を加えながら、古いフィールドと同じフィールドを共有する新しいオブジェクトを作成するcopy
メソッドを提供しcopy
。
この機能を使用して、以前と同じ特性を持つ新しいオブジェクトを作成することができます。この機能を示すシンプルなケースクラス:
case class Person(firstName: String, lastName: String, grade: String, subject: String)
val putu = Person("Putu", "Kevin", "A1", "Math")
val mark = putu.copy(firstName = "Ketut", lastName = "Mark")
// mark: People = People(Ketut,Mark,A1,Math)
この例では、2つのオブジェクトがコピー( firstName
とlastName
)に指定されている場合を除いて、2つのオブジェクトが類似の特性( grade = A1
、 subject = Math
)を共有していることがわかります。
型安全のための単一要素ケースクラス
型の安全性を達成するために、時には私たちのドメイン上でプリミティブ型の使用を避けたいと考えています。たとえば、 name
持つPerson
を想像してみてください。通常、 name
をString
としてエンコードします。ただし、 Person
のname
を表すString
エラーメッセージを表すString
を混在させることは難しくありません。
def logError(message: ErrorMessage): Unit = ???
case class Person(name: String)
val maybeName: Either[String, String] = ??? // Left is error, Right is name
maybeName.foreach(logError) // But that won't stop me from logging the name as an error!
このような落とし穴を避けるために、次のようにデータをエンコードすることができます:
case class PersonName(value: String)
case class ErrorMessage(value: String)
case class Person(name: PersonName)
PersonName
とErrorMessage
、さらには普通のString
を混ぜると、私たちのコードはコンパイルされません。
val maybeName: Either[ErrorMessage, PersonName] = ???
maybeName.foreach(reportError) // ERROR: tried to pass PersonName; ErrorMessage expected
maybeName.swap.foreach(reportError) // OK
しかしこれは、 PersonName
コンテナとの間でString
をボックス化/ボックス化解除する必要があるため、ランタイムオーバーヘッドが小さくなります。これを避けるために、 PersonName
クラスとErrorMessage
値クラスを作成できます。
case class PersonName(val value: String) extends AnyVal
case class ErrorMessage(val value: String) extends AnyVal