Ricerca…


Sintassi

  • case class Foo () // Le classi di casi senza parametri devono avere una lista vuota
  • case class Foo (a1: A1, ..., aN: AN) // Crea una classe case con i campi a1 ... aN
  • caso oggetto Barra // Crea una classe caso singleton

Case Classality

Una funzione fornita gratuitamente dalle classi case è un metodo di equals generato automaticamente che controlla l'uguaglianza di valore di tutti i singoli campi membro invece di controllare semplicemente l'uguaglianza di riferimento degli oggetti.

Con le classi ordinarie:

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

Con classi di casi:

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

Manufatti di codice generati

Il modificatore di case fa sì che il compilatore Scala generi automaticamente un codice standard di codice comune per la classe. L'implementazione manuale di questo codice è noiosa e fonte di errori. La seguente definizione della classe del caso:

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

... verrà generato automaticamente il seguente codice:

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

Il modificatore di case genera anche un oggetto associato:

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

Quando applicato a un object , il modificatore di case ha effetti simili (anche se meno drammatici). Qui i guadagni primari sono un'implementazione di toString e un valore hashCode coerente tra i processi. Nota che gli oggetti case (correttamente) usano l'uguaglianza di riferimento:

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

È ancora possibile implementare manualmente metodi che sarebbero altrimenti forniti dal modificatore di case sia nella classe stessa che nel suo oggetto associato.

Nozioni di base di Case Class

Rispetto alle classi regolari, la notazione delle classi di casi offre diversi vantaggi:

  • Tutti gli argomenti del costruttore sono public e accessibili su oggetti inizializzati (normalmente questo non è il caso, come dimostrato qui):

    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"
    
  • Fornisce un'implementazione per i seguenti metodi: toString , equals , hashCode (basato sulle proprietà), copy , apply e unapply :

    case class Dog(age: Int)
    val d1 = Dog(10)
    val d2 = d1.copy(age = 15)
    
  • Fornisce un comodo meccanismo per la corrispondenza del modello:

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

Classi di casi e immutabilità

Il compilatore Scala prefixa ogni argomento nella lista dei parametri per default con val . Ciò significa che, per impostazione predefinita, le classi dei casi sono immutabili. Ad ogni parametro viene assegnato un metodo di accesso, ma non esistono metodi di mutatore. Per esempio:

case class Foo(i: Int)

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

La dichiarazione di un parametro in una case case come var sovrascrive il comportamento predefinito e rende la classe case mutabile:

case class Bar(var i: Int)

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

Un'altra istanza in cui una classe case è "mutabile" è quando il valore nella classe case è mutabile:

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)

Si noti che la "mutazione" che si sta verificando qui è nella mappa a cui m indica, non a m stesso. Quindi, se qualche altro oggetto avesse m come membro, vedrebbe anche il cambiamento. Si noti come nel seguente esempio, la modifica instanceA modifica anche l' 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)

Crea una copia di un oggetto con determinate modifiche

Le classi dei casi forniscono un metodo di copy che crea un nuovo oggetto che condivide gli stessi campi di quello vecchio, con alcune modifiche.

Possiamo usare questa funzione per creare un nuovo oggetto da uno precedente che abbia alcune delle stesse caratteristiche. Questa semplice classe di casi per dimostrare questa caratteristica:

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 questo esempio possiamo vedere che i due oggetti condividono caratteristiche simili ( grade = A1 , subject = Math ), tranne dove sono stati specificati nella copia ( firstName e lastName ).

Classi di casi a singolo elemento per sicurezza di tipo

Al fine di raggiungere la sicurezza del tipo a volte vogliamo evitare l'uso di tipi primitivi sul nostro dominio. Ad esempio, immagina una Person con un name . In genere, dovremmo codificare il name come String . Tuttavia, non sarebbe difficile mescolare uno String che rappresenta una Person 's name con una String che rappresenta un messaggio di errore:

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!

Per evitare tali problemi puoi codificare i dati in questo modo:

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

e ora il nostro codice non verrà compilato se mescoliamo PersonName con ErrorMessage o anche una String ordinaria.

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

Ma questo comporta un piccolo sovraccarico di runtime dato che ora dobbiamo box / unbox String s to / from i loro contenitori PersonName . Per evitare ciò, è possibile creare classi di valore PersonName e 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
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow