Suche…


Syntax

  • Fallklasse Foo () // Fallklassen ohne Parameter müssen eine leere Liste haben
  • Fallklasse Foo (a1: A1, ..., aN: AN) // Eine Fallklasse mit den Feldern a1 ... aN erstellen
  • case object Bar // Eine Singleton-Case-Klasse erstellen

Fallklassengleichheit

Ein Feature, das von Fallklassen frei bereitgestellt wird, ist eine automatisch generierte equals Methode, die die Wertegleichheit aller einzelnen equals prüft, anstatt nur die Referenzgleichheit der Objekte zu prüfen.

Bei gewöhnlichen Klassen:

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

Mit Fallklassen:

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

Generierte Code-Artefakte

Der case Modifikator bewirkt , dass die Scala - Compiler automatisch gemeinsamen Standardcode für die Klasse generieren. Die manuelle Implementierung dieses Codes ist langwierig und eine Fehlerquelle. Die folgende Fallklassendefinition:

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

... wird der folgende Code automatisch generiert:

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

Der case Modifikator erzeugt auch ein Begleitobjekt:

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

Wenn auf ein angelegtes object , der case hat Modifikator ähnlich (wenn auch weniger dramatisch) Effekte. Hier sind die wichtigsten Vorteile eine toString Implementierung und ein über alle Prozesse hinweg konsistenter hashCode . Beachten Sie, dass Fallobjekte (korrekt) eine Referenzgleichheit verwenden:

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

Es ist immer noch möglich, manuell Methoden zu implementieren , die sonst durch den zur Verfügung gestellt werden würde case Modifikator sowohl in der Klasse selbst und seine Begleiter Objekt.

Fallklassen-Grundlagen

Im Vergleich zu regulären Klassen bietet die Notation von Fallklassen mehrere Vorteile:

  • Alle Konstruktorargumente sind public und können auf initialisierte Objekte zugegriffen werden (normalerweise ist dies nicht der Fall, wie hier gezeigt):

    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"
    
  • Es stellt eine Implementierung für die folgenden Methoden: toString , equals , hashCode (basierend auf Eigenschaften), copy , apply und unapply :

    case class Dog(age: Int)
    val d1 = Dog(10)
    val d2 = d1.copy(age = 15)
    
  • Es bietet einen praktischen Mechanismus für die Mustererkennung:

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

Fallklassen und Unveränderlichkeit

Der Scala-Compiler stellt jedem Argument in der Parameterliste standardmäßig den val . Dies bedeutet, dass Fallklassen standardmäßig unveränderlich sind. Jeder Parameter erhält eine Zugriffsmethode, es gibt jedoch keine Mutator-Methoden. Zum Beispiel:

case class Foo(i: Int)

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

Das Deklarieren eines Parameters in einer Fallklasse als var überschreibt das Standardverhalten und macht die Fallklasse veränderbar:

case class Bar(var i: Int)

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

Eine andere Instanz, in der eine Fallklasse "veränderbar" ist, ist der veränderliche Wert in der Fallklasse:

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)

Beachten Sie, dass die hier vorkommende 'Mutation' sich in der Map befindet, auf die m zeigt, nicht auf m selbst. Wenn also ein anderes Objekt m als Member hatte, würde es auch die Änderung sehen. Beachten Sie, wie im folgenden Beispiel die instanceA geändert wird, instanceB auch die instanceB B geändert wird:

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)

Erstellen Sie eine Kopie eines Objekts mit bestimmten Änderungen

Fallklassen bieten eine copy , mit der ein neues Objekt erstellt wird, das bei bestimmten Änderungen dieselben Felder wie das alte Objekt verwendet.

Wir können diese Funktion verwenden, um ein neues Objekt aus einem vorherigen Objekt zu erstellen, das einige der gleichen Merkmale aufweist. Diese einfache Fallklasse veranschaulicht diese Funktion:

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 diesem Beispiel sehen wir, dass die beiden Objekte ähnliche Merkmale aufweisen ( grade = A1 , subject = Math ), es sei denn, sie wurden in der Kopie ( firstName und lastName ) angegeben.

Einzelelement-Gehäuseklassen für die Typsicherheit

Um die Typsicherheit zu erreichen, möchten wir manchmal die Verwendung primitiver Typen in unserer Domäne vermeiden. Stellen Sie sich zum Beispiel eine Person mit einem name . Normalerweise würden wir den name als String kodieren. Allerdings wäre es nicht schwer sein , ein mischen String einer darstellt Person ‚s name mit einem String repräsentiert eine Fehlermeldung:

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!

Um solche Fehler zu vermeiden, können Sie die Daten folgendermaßen codieren:

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

und jetzt wird unser Code nicht kompiliert, wenn wir PersonName mit ErrorMessage oder sogar einen normalen String mischen.

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

Dies verursacht jedoch einen geringen Laufzeit-Overhead, da wir nun String s in / aus seinen PersonName Containern PersonName . Um dies zu vermeiden, können Klassen für PersonName und ErrorMessage Werte erstellt werden:

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
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow