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 inte unapply :

    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


Modified text is an extract of the original Stack Overflow Documentation
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow