web-dev-qa-db-fra.com

Quelle est la différence entre la classe de cas et la classe de Scala?

J'ai cherché dans Google pour trouver les différences entre un case class et un class. Tout le monde mentionne que lorsque vous souhaitez effectuer une correspondance de modèle dans la classe, utilisez la classe de cas. Sinon, utilisez les classes et mentionnez également des avantages supplémentaires, tels qu'égal et le code de hachage. Mais sont-ce les seules raisons pour lesquelles on devrait utiliser une classe de cas au lieu de classe?

Je suppose qu'il devrait y avoir une raison très importante pour cette fonctionnalité dans Scala. Quelle est l'explication ou existe-t-il une ressource pour en savoir plus sur les classes de cas Scala?

417
Teja Kantamneni

Les classes de cas peuvent être vues comme des objets simples et immuables contenant des données, qui doivent exclusivement dépendre de leurs arguments de constructeur .

Ce concept fonctionnel nous permet de

  • utiliser une syntaxe d'initialisation compacte (Node(1, Leaf(2), None)))
  • décomposez-les à l'aide d'une correspondance de motif
  • avoir des comparaisons d'égalité implicitement définies

En combinaison avec l'héritage, les classes de cas sont utilisées pour imiter types de données algébriques .

Si un objet effectue des calculs avec état à l'intérieur ou présente d'autres types de comportement complexe, il doit s'agir d'une classe ordinaire.

368
Dario

Techniquement, il n'y a pas de différence entre une classe et une classe de cas, même si le compilateur optimise certaines choses lorsqu'il utilise des classes de cas. Cependant, une classe de cas est utilisée pour supprimer la plaque de chaudière pour un modèle spécifique, qui implémente types de données algébriques .

Un exemple très simple de tels types sont les arbres. Un arbre binaire, par exemple, peut être implémenté comme ceci:

sealed abstract class Tree
case class Node(left: Tree, right: Tree) extends Tree
case class Leaf[A](value: A) extends Tree
case object EmptyLeaf extends Tree

Cela nous permet de faire ce qui suit:

// DSL-like assignment:
val treeA = Node(EmptyLeaf, Leaf(5))
val treeB = Node(Node(Leaf(2), Leaf(3)), Leaf(5))

// On Scala 2.8, modification through cloning:
val treeC = treeA.copy(left = treeB.left)

// Pretty printing:
println("Tree A: "+treeA)
println("Tree B: "+treeB)
println("Tree C: "+treeC)

// Comparison:
println("Tree A == Tree B: %s" format (treeA == treeB).toString)
println("Tree B == Tree C: %s" format (treeB == treeC).toString)

// Pattern matching:
treeA match {
  case Node(EmptyLeaf, right) => println("Can be reduced to "+right)
  case Node(left, EmptyLeaf) => println("Can be reduced to "+left)
  case _ => println(treeA+" cannot be reduced")
}

// Pattern matches can be safely done, because the compiler warns about
// non-exaustive matches:
def checkTree(t: Tree) = t match {
  case Node(EmptyLeaf, Node(left, right)) =>
  // case Node(EmptyLeaf, Leaf(el)) =>
  case Node(Node(left, right), EmptyLeaf) =>
  case Node(Leaf(el), EmptyLeaf) =>
  case Node(Node(l1, r1), Node(l2, r2)) =>
  case Node(Leaf(e1), Leaf(e2)) =>
  case Node(Node(left, right), Leaf(el)) =>
  case Node(Leaf(el), Node(left, right)) =>
  // case Node(EmptyLeaf, EmptyLeaf) =>
  case Leaf(el) =>
  case EmptyLeaf =>
}

Notez que les arbres construisent et déconstruisent (par correspondance de modèle) avec la même syntaxe, qui correspond également à la manière dont ils sont imprimés (espaces moins).

Et ils peuvent également être utilisés avec des cartes de hachage ou des ensembles, car ils ont un hashCode valide et stable.

157
Daniel C. Sobral
  • Les classes de cas peuvent être associées à un modèle
  • Les classes de cas définissent automatiquement hashcode et equals
  • Les classes de cas définissent automatiquement les méthodes getter pour les arguments du constructeur.

(Vous avez déjà mentionné tout sauf le dernier).

Ce sont les seules différences aux classes ordinaires.

63
sepp2k

Personne n'a mentionné que les classes de cas sont également des instances de Product et héritent donc de ces méthodes:

def productElement(n: Int): Any
def productArity: Int
def productIterator: Iterator[Any]

productArity renvoie le nombre de paramètres de classe, productElement(i) renvoie le ith paramètre, et productIterator permet de les parcourir.

27

Personne n'a mentionné que les classes de cas ont val paramètres de constructeur, mais c'est aussi la valeur par défaut pour les classes régulières (qui je pense que c'est une incohérence dans la conception de Scala). Dario a laissé entendre qu'il était " immuable ".

Notez que vous pouvez remplacer la valeur par défaut en ajoutant le début de chaque argument de constructeur avec var pour les classes de cas. Cependant, rendre les classes de cas mutables rend leurs méthodes equals et hashCode variant avec le temps. [1]

sepp2k déjà mentionné, les classes de cas génèrent automatiquement les méthodes equals et hashCode.

De plus, personne n'a mentionné que les classes de cas créent automatiquement un compagnon object avec le même nom que la classe, qui contient les méthodes apply et unapply. La méthode apply permet de construire des instances sans ajouter de préfixe avec new. La méthode d'extraction unapply active la correspondance de motif mentionnée par d'autres.

De plus, le compilateur optimise la vitesse de correspondance de modèle match-case pour les classes de cas [2].

[1] Les classes de cas sont cool

[2] Classes de cas et extracteurs, p. 15 .

26
Shelby Moore III

La construction de classe de cas dans Scala peut également être considérée comme un moyen pratique de supprimer un certain standard.

Lors de la construction d'une classe de cas, Scala vous donne les informations suivantes.

  • Il crée une classe ainsi que son objet compagnon
  • Son objet compagnon implémente la méthode apply que vous pouvez utiliser comme méthode par défaut. Vous bénéficiez de l’avantage en sucre syntaxique de ne pas avoir à utiliser le nouveau mot-clé.

Parce que la classe est immuable, vous obtenez des accesseurs, qui ne sont que les variables (ou propriétés) de la classe mais pas de mutateurs (donc pas de possibilité de changer les variables). Les paramètres du constructeur sont automatiquement disponibles sous forme de champs en lecture seule. Bien plus agréable à utiliser que la construction de bean Java.

  • Vous obtenez également les méthodes hashCode, equals et toString par défaut et la méthode equals compare structurellement un objet. Une méthode copy est générée pour pouvoir cloner un objet (certains champs ayant de nouvelles valeurs fournies à la méthode).

Le principal avantage, comme cela a été mentionné précédemment, est le fait que vous pouvez appliquer des motifs sur des classes de cas. La raison en est que vous obtenez la méthode unapply qui vous permet de déconstruire une classe de cas pour extraire ses champs.


Essentiellement, ce que vous obtenez de Scala lors de la création d'une classe de cas (ou d'un objet de cas si votre classe ne prend pas d'argument) est un objet singleton qui remplit sa fonction en tant que fabrique et en tant que extracteur.

9
Faktor 10

Outre ce que les gens ont déjà dit, il existe quelques différences plus fondamentales entre class et case class

1 .Case Class n'a pas besoin de new explicite, alors que la classe doit être appelée avec new

val classInst = new MyClass(...)  // For classes
val classInst = MyClass(..)       // For case class

2.Les paramètres des constructeurs par défaut sont privés dans class, alors que ses paramètres publics dans case class

// For class
class MyClass(x:Int) { }
val classInst = new MyClass(10)

classInst.x   // FAILURE : can't access

// For caseClass
case class MyClass(x:Int) { }
val classInst = MyClass(10)

classInst.x   // SUCCESS

3 .case class se comparent par valeur

// case Class
class MyClass(x:Int) { }

val classInst = new MyClass(10)
val classInst2 = new MyClass(10)

classInst == classInst2 // FALSE

// For Case Class
case class MyClass(x:Int) { }

val classInst = MyClass(10)
val classInst2 = MyClass(10)

classInst == classInst2 // TRUE
6
DeepakKg

Selon Scala's documentation :

Les classes de cas ne sont que des classes régulières qui sont:

  • Immuable par défaut
  • Décomposable à travers correspondance de motif
  • Comparé par égalité structurelle plutôt que par référence
  • Succinct pour instancier et opérer

Une autre caractéristique du mot clé case est que le compilateur génère automatiquement plusieurs méthodes, y compris les méthodes toString, equals et hashCode, bien connues en Java.

5
user2989087

Classe:

scala> class Animal(name:String)
defined class Animal

scala> val an1 = new Animal("Padddington")
an1: Animal = Animal@748860cc

scala> an1.name
<console>:14: error: value name is not a member of Animal
       an1.name
           ^

Mais si nous utilisons le même code mais en utilisant la classe de cas:

scala> case class Animal(name:String)
defined class Animal

scala> val an2 = new Animal("Paddington")
an2: Animal = Animal(Paddington)

scala> an2.name
res12: String = Paddington


scala> an2 == Animal("fred")
res14: Boolean = false

scala> an2 == Animal("Paddington")
res15: Boolean = true

Classe de personne:

scala> case class Person(first:String,last:String,age:Int)
defined class Person

scala> val harry = new Person("Harry","Potter",30)
harry: Person = Person(Harry,Potter,30)

scala> harry
res16: Person = Person(Harry,Potter,30)
scala> harry.first = "Saily"
<console>:14: error: reassignment to val
       harry.first = "Saily"
                   ^
scala>val saily =  harry.copy(first="Saily")
res17: Person = Person(Saily,Potter,30)

scala> harry.copy(age = harry.age+1)
res18: Person = Person(Harry,Potter,31)

Correspondance de modèle:

scala> harry match {
     | case Person("Harry",_,age) => println(age)
     | case _ => println("no match")
     | }
30

scala> res17 match {
     | case Person("Harry",_,age) => println(age)
     | case _ => println("no match")
     | }
no match

objet: singleton:

scala> case class Person(first :String,last:String,age:Int)
defined class Person

scala> object Fred extends Person("Fred","Jones",22)
defined object Fred
4
user1668782

Pour avoir la compréhension ultime de ce qu'est une classe de cas:

supposons la définition de classe de cas suivante:

case class Foo(foo:String, bar: Int)

puis procédez comme suit dans le terminal:

$ scalac -print src/main/scala/Foo.scala

Scala 2.12.8 produira:

...
case class Foo extends Object with Product with Serializable {

  <caseaccessor> <paramaccessor> private[this] val foo: String = _;

  <stable> <caseaccessor> <accessor> <paramaccessor> def foo(): String = Foo.this.foo;

  <caseaccessor> <paramaccessor> private[this] val bar: Int = _;

  <stable> <caseaccessor> <accessor> <paramaccessor> def bar(): Int = Foo.this.bar;

  <synthetic> def copy(foo: String, bar: Int): Foo = new Foo(foo, bar);

  <synthetic> def copy$default$1(): String = Foo.this.foo();

  <synthetic> def copy$default$2(): Int = Foo.this.bar();

  override <synthetic> def productPrefix(): String = "Foo";

  <synthetic> def productArity(): Int = 2;

  <synthetic> def productElement(x$1: Int): Object = {
    case <synthetic> val x1: Int = x$1;
        (x1: Int) match {
            case 0 => Foo.this.foo()
            case 1 => scala.Int.box(Foo.this.bar())
            case _ => throw new IndexOutOfBoundsException(scala.Int.box(x$1).toString())
        }
  };

  override <synthetic> def productIterator(): Iterator = scala.runtime.ScalaRunTime.typedProductIterator(Foo.this);

  <synthetic> def canEqual(x$1: Object): Boolean = x$1.$isInstanceOf[Foo]();

  override <synthetic> def hashCode(): Int = {
     <synthetic> var acc: Int = -889275714;
     acc = scala.runtime.Statics.mix(acc, scala.runtime.Statics.anyHash(Foo.this.foo()));
     acc = scala.runtime.Statics.mix(acc, Foo.this.bar());
     scala.runtime.Statics.finalizeHash(acc, 2)
  };

  override <synthetic> def toString(): String = scala.runtime.ScalaRunTime._toString(Foo.this);

  override <synthetic> def equals(x$1: Object): Boolean = Foo.this.eq(x$1).||({
      case <synthetic> val x1: Object = x$1;
        case5(){
          if (x1.$isInstanceOf[Foo]())
            matchEnd4(true)
          else
            case6()
        };
        case6(){
          matchEnd4(false)
        };
        matchEnd4(x: Boolean){
          x
        }
    }.&&({
      <synthetic> val Foo$1: Foo = x$1.$asInstanceOf[Foo]();
      Foo.this.foo().==(Foo$1.foo()).&&(Foo.this.bar().==(Foo$1.bar())).&&(Foo$1.canEqual(Foo.this))
  }));

  def <init>(foo: String, bar: Int): Foo = {
    Foo.this.foo = foo;
    Foo.this.bar = bar;
    Foo.super.<init>();
    Foo.super./*Product*/$init$();
    ()
  }
};

<synthetic> object Foo extends scala.runtime.AbstractFunction2 with Serializable {

  final override <synthetic> def toString(): String = "Foo";

  case <synthetic> def apply(foo: String, bar: Int): Foo = new Foo(foo, bar);

  case <synthetic> def unapply(x$0: Foo): Option =
     if (x$0.==(null))
        scala.None
     else
        new Some(new Tuple2(x$0.foo(), scala.Int.box(x$0.bar())));

  <synthetic> private def readResolve(): Object = Foo;

  case <synthetic> <bridge> <artifact> def apply(v1: Object, v2: Object): Object = Foo.this.apply(v1.$asInstanceOf[String](), scala.Int.unbox(v2));

  def <init>(): Foo.type = {
    Foo.super.<init>();
    ()
  }
}
...

Comme nous pouvons le constater, le compilateur Scala produit une classe régulière Foo et un objet compagnon Foo.

Passons en revue la classe compilée et commentons ce que nous avons:

  • l'état interne de la classe Foo, immuable:
val foo: String
val bar: Int
  • getters:
def foo(): String
def bar(): Int
  • méthodes de copie:
def copy(foo: String, bar: Int): Foo
def copy$default$1(): String
def copy$default$2(): Int
  • implémentation de scala.Product trait:
override def productPrefix(): String
def productArity(): Int
def productElement(x$1: Int): Object
override def productIterator(): Iterator
  • implémentation de scala.Equals trait pour rendre les instances de classe de cas comparables pour égalité par ==:
def canEqual(x$1: Object): Boolean
override def equals(x$1: Object): Boolean
  • écrasant Java.lang.Object.hashCode pour obéir au contrat de code de hachage:
override <synthetic> def hashCode(): Int
  • remplacer Java.lang.Object.toString:
override def toString(): String
  • constructeur pour l'instanciation par new mot-clé:
def <init>(foo: String, bar: Int): Foo 

Objet Foo: - méthode apply pour une instanciation sans new mot-clé:

case <synthetic> def apply(foo: String, bar: Int): Foo = new Foo(foo, bar);
  • méthode d'extraction unupply pour utiliser la classe de cas Foo dans la recherche de modèle:
case <synthetic> def unapply(x$0: Foo): Option
  • méthode pour protéger objet comme singleton de la désérialisation pour ne pas laisser produire une instance supplémentaire:
<synthetic> private def readResolve(): Object = Foo;
  • l'objet Foo étend scala.runtime.AbstractFunction2 pour avoir fait telle astuce:
scala> case class Foo(foo:String, bar: Int)
defined class Foo

scala> Foo.tupled
res1: ((String, Int)) => Foo = scala.Function2$$Lambda$224/1935637221@9ab310b

tupled from object renvoie une fonction permettant de créer un nouveau Foo en appliquant un tuple de 2 éléments.

Donc, la classe de cas est juste le sucre syntaxique.

3
Matthew I.

Contrairement aux classes, les classes de cas ne sont utilisées que pour stocker des données.

Les classes de cas sont flexibles pour les applications centrées sur les données, ce qui signifie que vous pouvez définir des champs de données dans une classe de cas et définir une logique métier dans un objet compagnon. De cette manière, vous séparez les données de la logique applicative.

Avec la méthode copy, vous pouvez hériter de toutes les propriétés requises de la source et les modifier à votre guise.

2
Reddeiah Pidugu

Personne n'a mentionné que l'objet compagnon de classe de cas a tupled defention, qui a le type:

case class Person(name: String, age: Int)
//Person.tupled is def tupled: ((String, Int)) => Person

Le seul cas d'utilisation que je puisse trouver est lorsqu'il est nécessaire de construire une classe de cas à partir de Tuple, par exemple:

val bobAsTuple = ("bob", 14)
val bob = (Person.apply _).tupled(bobAsTuple) //bob: Person = Person(bob,14)

Vous pouvez faire la même chose, sans multiplier, en créant un objet directement, mais si vous préférez que vos ensembles de données soient exprimés sous forme de liste de Tuple avec arité 20 (Tuple avec 20 éléments), vous pouvez utiliser la syntaxe.

2
Slow Harry

Une classe de cas est une classe pouvant être utilisée avec l'instruction match/case.

def isIdentityFun(term: Term): Boolean = term match {
  case Fun(x, Var(y)) if x == y => true
  case _ => false
}

Vous voyez que case est suivi d'une instance de la classe Fun dont le second paramètre est un Var. C'est une syntaxe très agréable et puissante, mais elle ne peut pas fonctionner avec les instances d'une classe, il y a donc des restrictions pour les classes de cas. Et si ces restrictions sont respectées, il est possible de définir automatiquement hashcode et equals.

L'expression vague "un mécanisme de décomposition récursif via un filtrage par motif" signifie simplement "cela fonctionne avec case". (En effet, l'instance suivie par match est comparée à (appariée à) l'instance qui suit case, Scala doit les décomposer tous les deux et doit décomposer ce qu'ils sont récursivement. fait de.)

Quelles sont les classes de cas ? Le article de Wikipedia sur les types de données algébriques donne deux bons exemples classiques, des listes et des arbres. La prise en charge des types de données algébriques (y compris savoir les comparer) est indispensable pour tout langage fonctionnel moderne.

À quoi les classes de cas ne sont-elles pas utiles ? Certains objets ont un état, le code comme connection.setConnectTimeout(connectTimeout) n'est pas destiné aux classes de cas.

Et maintenant, vous pouvez lire n tour de Scala: Classes d’affaires

2

Je pense que dans l’ensemble, toutes les réponses ont donné une explication sémantique sur les classes et les classes de cas. Cela pourrait être très pertinent, mais chaque débutant dans scala devrait savoir ce qui se passe lorsque vous créez une classe de cas. J'ai écrit this answer, ce qui explique la classe de cas en un mot.

Tous les programmeurs doivent savoir que s’ils utilisent des fonctions prédéfinies, ils écrivent un code comparativement moins important, ce qui leur permet de rédiger le code le plus optimisé, mais le pouvoir comporte de grandes responsabilités. Donc, utilisez des fonctions prédéfinies avec beaucoup de précautions.

Certains développeurs évitent d'écrire des classes de cas en raison de 20 méthodes supplémentaires, que vous pouvez voir en désassemblant un fichier de classe.

S'il vous plaît référez-vous à ce lien si vous souhaitez vérifier toutes les méthodes d'une classe de cas .

0
arglee
  • Les classes de cas définissent un objet compagnon avec les méthodes apply et unapply
  • Classes de cas étendus Serializable
  • Les classes de cas définissent les méthodes hashCode et copy égales
  • Tous les attributs du constructeur sont val (sucre syntaxique)
0
tictactoki

Certaines des principales caractéristiques de case classes sont énumérées ci-dessous.

  1. les classes de cas sont immuables.
  2. Vous pouvez instancier des classes de cas sans le mot clé new.
  3. les classes de cas peuvent être comparées par valeur

Exemple de code scala sur scala fiddle, extrait du scala docs.

https://scalafiddle.io/sf/34XEQyE/

0
Krishnadas PC