Scala Language
Fallklasser
Sök…
Syntax
- case class Foo () // Fallklasser utan parametrar måste ha en tom lista
- fall klass Foo (a1: A1, ..., aN: AN) // Skapa en fallklass med fält a1 ... aN
- case object Bar // Skapa en singleton case class
Case Class Equality
En funktion som tillhandahålls gratis av fallklasser är en auto-genererad equals
metod som kontrollerar värdet jämlikhet för alla enskilda medlemsfält istället för att bara kontrollera objektens referenslikhet.
Med vanliga klasser:
class Foo(val i: Int)
val a = new Foo(3)
val b = new Foo(3)
println(a == b)// "false" because they are different objects
Med fallklasser:
case class Foo(i: Int)
val a = Foo(3)
val b = Foo(3)
println(a == b)// "true" because their members have the same value
Genererade kodföremål
Det case
modifierings orsakar kompilatorn Scala för att automatiskt generera gemensam standardtext kod för klassen. Att implementera denna kod manuellt är tråkigt och en källa till fel. Följande fallklassdefinition:
case class Person(name: String, age: Int)
... kommer följande kod att genereras automatiskt:
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)
}
Det case
modifierings genererar också en följeslagare objekt:
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))
}
När den appliceras på ett object
, det case
har modifierings liknande (om än mindre dramatiska) effekter. Här är de primära vinsterna en toString
implementering och ett hashCode
värde som är konsekvent mellan processer. Observera att fallobjekt (korrekt) använder jämställdhet:
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"
}
Är det fortfarande möjligt att manuellt implementera metoder som annars skulle tillhandahållas av det case
modifieraren i både klassen själv och dess följeobjekt.
Grunder i fallklass
I jämförelse med vanliga klasser - notering av fallklasser ger flera fördelar:
Alla konstruktörargument är
public
och kan nås på initialiserade objekt (normalt är detta inte fallet, som visas här):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"
Den tillhandahåller en implementering för följande metoder:
toString
,equals
,hashCode
(baserat på egenskaper),copy
,apply
och inteunapply
:case class Dog(age: Int) val d1 = Dog(10) val d2 = d1.copy(age = 15)
Det ger en bekväm mekanism för mönstermatchning:
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.") }
Ärendekurser och immutabilty
Scala-kompilatorn förinställer som standard alla parametrar i parameterlistan med val
. Detta innebär att ärendeklasser som standard är oföränderliga. Varje parameter ges en accessor-metod, men det finns inga mutatormetoder. Till exempel:
case class Foo(i: Int)
val fooInstance = Foo(1)
val j = fooInstance.i // get
fooInstance.i = 2 // compile-time exception (mutation: reassignment to val)
Att förklara en parameter i en fallklass som var
åsidosätter standardbeteendet och gör ärendeklassen mutabel:
case class Bar(var i: Int)
val barInstance = Bar(1)
val j = barInstance.i // get
barInstance.i = 2 // set
En annan instans när en ärendeklass är 'muterbar' är när värdet i ärendeklassen är muterbart:
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)
Observera att "mutationen" som sker här är på kartan som m
pekar på, inte mot m
själv. Således, om något annat objekt hade m
som medlem, skulle det också se förändringen. Observera hur i följande exempel ändrar instanceA
också ändrar 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)
Skapa en kopia av ett objekt med vissa ändringar
Fallklasser tillhandahåller en copy
som skapar ett nytt objekt som delar samma fält som det gamla med vissa förändringar.
Vi kan använda den här funktionen för att skapa ett nytt objekt från ett tidigare objekt som har några av samma egenskaper. Denna enkla fallklass för att visa den här funktionen:
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)
I det här exemplet kan vi se att de två objekten har liknande egenskaper ( grade = A1
, subject = Math
), utom där de har angetts i kopian ( firstName
och lastName
).
Fallelement med enstaka element för typsäkerhet
För att uppnå typsäkerhet vill vi ibland undvika att använda primitiva typer på vår domän. Föreställ dig till exempel en Person
med ett name
. Vanligtvis skulle vi koda name
som en String
. Det skulle dock inte vara svårt att blanda en String
representerar en Person
name
med en String
representerar ett felmeddelande:
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!
För att undvika sådana fallgropar kan du koda data så här:
case class PersonName(value: String)
case class ErrorMessage(value: String)
case class Person(name: PersonName)
och nu kommer vår kod inte att sammanställas om vi blandar PersonName
med ErrorMessage
, eller till och med en vanlig String
.
val maybeName: Either[ErrorMessage, PersonName] = ???
maybeName.foreach(reportError) // ERROR: tried to pass PersonName; ErrorMessage expected
maybeName.swap.foreach(reportError) // OK
Men detta medför en liten drifttid som vi nu måste boxa / avlasta String
till / från deras PersonName
. För att undvika detta kan man göra PersonName
och ErrorMessage
:
case class PersonName(val value: String) extends AnyVal
case class ErrorMessage(val value: String) extends AnyVal