Zoeken…


Syntaxis

  • case-klasse Foo () // Case-klassen zonder parameters moeten een lege lijst hebben
  • case-klasse Foo (a1: A1, ..., aN: AN) // Maak een case-klasse met velden a1 ... aN
  • case object Bar // Maak een singleton case-klasse

Case Class Equality

Een functie die gratis wordt aangeboden door case-klassen is een automatisch gegenereerde methode voor equals die de waarde-gelijkheid van alle individuele ledenvelden controleert in plaats van alleen de referentie-gelijkheid van de objecten te controleren.

Met gewone lessen:

class Foo(val i: Int)
val a = new Foo(3)
val b = new Foo(3)
println(a == b)// "false" because they are different objects

Met casusklassen:

case class Foo(i: Int)
val a = Foo(3)
val b = Foo(3)
println(a == b)// "true" because their members have the same value

Gegenereerde code-artefacten

De case modifier zorgt ervoor dat de Scala-compiler automatisch gemeenschappelijke boilerplate-code voor de klasse genereert. Het handmatig implementeren van deze code is vervelend en een bron van fouten. De volgende definitie van de case-klasse:

case class Person(name: String, age: Int)

... wordt de volgende code automatisch gegenereerd:

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)
}

De case modifier genereert ook een bijbehorend object:

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))
}

Wanneer toegepast op een object , heeft de case modifier vergelijkbare (zij het minder dramatische) effecten. Hier zijn de belangrijkste voordelen een toString implementatie en een hashCode waarde die consistent is in alle processen. Merk op dat caseobjecten (correct) referentie-gelijkheid gebruiken:

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"
}

Het is nog steeds mogelijk om handmatig methoden te implementeren die anders door de case modifier zouden worden geleverd in zowel de klasse zelf als het bijbehorende object.

Basiscursus case-klasse

In vergelijking met reguliere klassen biedt notatie van case-klassen verschillende voordelen:

  • Alle constructorargumenten zijn public en zijn toegankelijk voor geïnitialiseerde objecten (normaal is dit niet het geval, zoals hier wordt aangetoond):

    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"
    
  • Het biedt een implementatie voor de volgende methoden: toString , is equals , hashCode (op basis van eigenschappen), copy , apply en niet unapply :

    case class Dog(age: Int)
    val d1 = Dog(10)
    val d2 = d1.copy(age = 15)
    
  • Het biedt een handig mechanisme voor het matchen van patronen:

    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.")
    }
    

Casusklassen en onveranderlijkheid

De Scala-compiler voorvoegt elk argument in de parameterlijst standaard met val . Dit betekent dat standaardklassen onveranderlijk zijn. Elke parameter krijgt een accessormethode, maar er zijn geen mutatiemethoden. Bijvoorbeeld:

case class Foo(i: Int)

val fooInstance = Foo(1)
val j = fooInstance.i       // get
fooInstance.i = 2           // compile-time exception (mutation: reassignment to val)

Het declareren van een parameter in een case-klasse als var overschrijft het standaardgedrag en maakt de case-klasse veranderlijk:

case class Bar(var i: Int)

val barInstance = Bar(1)
val j = barInstance.i       // get
barInstance.i = 2           // set

Een ander voorbeeld wanneer een case-klasse 'mutable' is, is wanneer de waarde in de case-class veranderlijk is:

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)

Merk op dat de 'mutatie' die hier plaatsvindt op de kaart staat waar m wijst, niet naar m zelf. Dus als een ander object m als lid zou hebben, zou het ook de verandering zien. Merk op hoe in het volgende voorbeeld instanceA ook instanceB verandert:

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)

Maak een kopie van een object met bepaalde wijzigingen

Casusklassen bieden een copy waarmee een nieuw object wordt gemaakt dat dezelfde velden deelt als het oude, met bepaalde wijzigingen.

We kunnen deze functie gebruiken om een nieuw object te maken van een vorig object dat enkele van dezelfde kenmerken heeft. Deze eenvoudige casusklasse om deze functie te demonstreren:

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)

In dit voorbeeld kunnen we zien dat de twee objecten delen vergelijkbare kenmerken ( grade = A1 , subject = Math ), tenzij zij hebben in de kopie (gespecificeerd firstName en lastName ).

Gevalklassen uit één element voor type veiligheid

Om typeveiligheid te bereiken, willen we soms het gebruik van primitieve typen op ons domein vermijden. Stel je bijvoorbeeld een Person met een name . Doorgaans coderen we de name als een String . Het zou echter niet moeilijk zijn om een String die de name van een Person vertegenwoordigt te Person met een String die een foutbericht weergeeft:

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!

Om dergelijke valkuilen te voorkomen, kunt u de gegevens als volgt coderen:

case class PersonName(value: String)
case class ErrorMessage(value: String)
case class Person(name: PersonName)

en nu onze code zal niet compileren als we mengen PersonName met ErrorMessage , of zelfs een gewone String .

val maybeName: Either[ErrorMessage, PersonName] = ???
maybeName.foreach(reportError) // ERROR: tried to pass PersonName; ErrorMessage expected
maybeName.swap.foreach(reportError) // OK

Maar dit brengt een korte runtime-overhead met zich mee, omdat we nu String s naar / van hun PersonName containers moeten in- / uitschakelen. Om dit te voorkomen, kan men maken PersonName en ErrorMessage waarde klassen:

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
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow