Szukaj…


Składnia

  • case class Foo () // Klasy przypadków bez parametrów muszą mieć pustą listę
  • klasa sprawy Foo (a1: A1, ..., aN: AN) // Utwórz klasę sprawy z polami a1 ... aN
  • obiekt sprawy Bar // Utwórz singletonową klasę sprawy

Równość klas przypadków

Jedną z funkcji udostępnianych za darmo przez klasy spraw jest automatycznie generowana metoda equals , która sprawdza równość wartości wszystkich poszczególnych pól członka zamiast tylko sprawdzać referencyjną równość obiektów.

Ze zwykłymi klasami:

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

Z klasami spraw:

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

Wygenerowane artefakty kodu

Modyfikator case powoduje, że kompilator Scala automatycznie generuje wspólny kod wzorcowy dla klasy. Ręczne wdrożenie tego kodu jest żmudne i powoduje błędy. Następująca definicja klasy przypadku:

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

... automatycznie wygeneruje następujący kod:

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

Modyfikator case generuje również obiekt towarzyszący:

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

Po zastosowaniu do object modyfikator wielkości case ma podobne (choć mniej dramatyczne) efekty. Tutaj podstawowymi korzyściami są implementacja toString i wartość hashCode która jest spójna między procesami. Zauważ, że obiekty przypadków (poprawnie) używają równości referencji:

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

Nadal możliwe jest ręczne wdrożenie metod, które w innym przypadku byłyby zapewniane przez modyfikator wielkości case zarówno w samej klasie, jak i jej obiekcie towarzyszącym.

Podstawy klasy przypadków

W porównaniu do klas regularnych - notacja klas przypadków zapewnia kilka korzyści:

  • Wszystkie argumenty konstruktora są public i można uzyskać do nich dostęp na zainicjowanych obiektach (zwykle tak nie jest, jak pokazano tutaj):

    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"
    
  • Zapewnia implementację następujących metod: toString , equals , hashCode (na podstawie właściwości), copy , apply i unapply :

    case class Dog(age: Int)
    val d1 = Dog(10)
    val d2 = d1.copy(age = 15)
    
  • Zapewnia wygodny mechanizm dopasowywania wzorów:

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

Klasy przypadków i niezmienność

Kompilator Scala poprzedza domyślnie każdy argument na liście parametrów val . Oznacza to, że domyślnie klasy przypadków są niezmienne. Każdy parametr ma metodę dostępu, ale nie ma metod mutatora. Na przykład:

case class Foo(i: Int)

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

Zadeklarowanie parametru w klasie przypadków jako var zastępuje zachowanie domyślne i powoduje, że klasa przypadków jest modyfikowalna:

case class Bar(var i: Int)

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

Innym przypadkiem, gdy klasa sprawy jest „mutowalna”, to gdy wartość w klasie przypadków jest zmienna:

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)

Zauważ, że występująca tutaj „mutacja” znajduje się na mapie, na którą wskazuje m , a nie na samą m . Tak więc, jeśli jakiś inny obiekt miałby m jako element członkowski, również zobaczyłby zmianę. Zwróć uwagę, jak w poniższym przykładzie zmiana instanceA zmienia również 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)

Utwórz kopię obiektu z pewnymi zmianami

Klasy spraw zapewniają metodę copy , która tworzy nowy obiekt, który ma takie same pola jak stary, z pewnymi zmianami.

Możemy użyć tej funkcji, aby utworzyć nowy obiekt z poprzedniego, który ma niektóre z tych samych cech. Ta prosta klasa przypadków demonstrująca tę funkcję:

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)

W tym przykładzie widzimy, że dwa obiekty mają podobne cechy ( grade = A1 , subject = Math ), z wyjątkiem przypadków, gdy zostały one określone w kopii ( firstName i lastName ).

Klasy pojedynczych elementów dla bezpieczeństwa typu

Aby osiągnąć bezpieczeństwo typów, czasami chcemy unikać używania typów pierwotnych w naszej domenie. Na przykład wyobraź sobie Person o name . Zwykle kodowalibyśmy name jako String . Jednak nie byłoby trudno mieszać String reprezentujący name Person z String reprezentującym komunikat o błędzie:

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!

Aby uniknąć takich pułapek, możesz zakodować dane w następujący sposób:

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

i teraz nasz kod nie będzie kompilować jeśli mieszamy PersonName z ErrorMessage , albo nawet zwykły String .

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

Ale to pociąga za sobą niewielki narzut Runtime jak mamy teraz do skrzynki / unbox String s do / z ich PersonName pojemników. Aby tego uniknąć, można utworzyć klasy wartości PersonName i 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
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow