В чем разница между классом дел Скалы и классом? - PullRequest
411 голосов
/ 22 февраля 2010

Я искал в Google, чтобы найти различия между case class и class. Все упоминают, что когда вы хотите сделать сопоставление с образцом в классе, используйте case case. В противном случае используйте классы, а также упомяните некоторые дополнительные привилегии, такие как equals и переопределение хеш-кода. Но являются ли это единственными причинами, по которым следует использовать класс case вместо класса?

Я полагаю, в Scala должна быть очень важная причина для этой функции. Каково объяснение или есть ресурс, чтобы узнать больше о классах случаев Scala?

Ответы [ 15 ]

361 голосов
/ 22 февраля 2010

Классы Case можно рассматривать как простые и неизменные объекты хранения данных, которые должны исключительно зависеть от аргументов своего конструктора .

Эта функциональная концепция позволяет нам

  • использовать компактный синтаксис инициализации (Node(1, Leaf(2), None)))
  • разложить их, используя сопоставление с образцом
  • имеют сравнения равенства, неявно определенные

В сочетании с наследованием классы падежей используются для имитации алгебраических типов данных .

Если объект выполняет вычисления с состоянием изнутри или демонстрирует другие виды сложного поведения, это должен быть обычный класс.

156 голосов
/ 23 февраля 2010

Технически, нет никакой разницы между классом и классом case - даже если компилятор оптимизирует некоторые вещи при использовании case-классов. Тем не менее, класс дел используется для того, чтобы покончить с котельной для определенного шаблона, который реализует алгебраические типы данных .

Очень простым примером таких типов являются деревья. Например, двоичное дерево может быть реализовано так:

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

Это позволяет нам делать следующее:

// 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 =>
}

Обратите внимание, что деревья конструируют и деконструируют (с помощью сопоставления с образцом) с одинаковым синтаксисом, который также является точным способом их печати (минус пробелы).

И их также можно использовать с хэш-картами или наборами, поскольку они имеют действительный стабильный хэш-код.

62 голосов
/ 22 февраля 2010
  • Классы дел могут быть сопоставлены с шаблоном
  • Классы дел автоматически определяют хеш-код и равны
  • Классы Case автоматически определяют методы-получатели для аргументов конструктора.

(Вы уже упомянули все, кроме последнего).

Это единственные отличия от обычных классов.

25 голосов
/ 07 сентября 2013

Никто не упомянул, что у case-классов есть val параметры конструктора, но это также по умолчанию для обычных классов (что я считаю несоответствием в дизайне Scala). Дарио подразумевал такие, где он отметил, что они " неизменны ".

Обратите внимание, что вы можете переопределить значение по умолчанию, добавив к каждому аргументу конструктора var для классов case. Однако, делая изменяемые классы case приводит к тому, что их методы equals и hashCode изменяются по времени. [1]

sepp2k уже упоминал, что case-классы автоматически генерируют методы equals и hashCode.

Также никто не упомянул, что case-классы автоматически создают компаньона object с тем же именем, что и класс, который содержит apply и unapply методы. Метод apply позволяет создавать экземпляры без добавления new. unapply метод извлечения включает сопоставление с шаблоном, который упоминали другие.

Также компилятор оптимизирует скорость сопоставления с шаблоном match - case для классов case [2].

[1] Классы кейсов классные

[2] Классы кейсов и экстракторы, стр. 15 .

25 голосов
/ 13 января 2011

Никто не упомянул, что case-классы также являются экземплярами Product и, таким образом, наследуют эти методы:

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

, где productArity возвращает количество параметров класса, productElement(i) возвращает параметр i th , а productIterator позволяет выполнять итерацию по ним.

9 голосов
/ 29 мая 2015

Конструкция класса case в Scala также может рассматриваться как удобство для удаления некоторого шаблона.

При построении класса дел Scala дает вам следующее.

  • Создает класс и объект-компаньон
  • Его сопутствующий объект реализует метод apply, который вы можете использовать в качестве заводского метода. Вы получаете преимущество синтаксического сахара в том, что вам не нужно использовать новое ключевое слово.

Поскольку класс неизменен, вы получаете методы доступа, которые являются просто переменными (или свойствами) класса, но не имеют мутаторов (поэтому нет возможности изменять переменные). Параметры конструктора автоматически доступны для вас как общедоступные поля только для чтения. Гораздо приятнее в использовании, чем конструкция Java bean.

  • По умолчанию вы также получаете методы hashCode, equals и toString, а метод equals структурно сравнивает объект. Метод copy генерируется, чтобы иметь возможность клонировать объект (некоторые поля имеют новые значения, предоставленные методу).

Самым большим преимуществом, как было упомянуто ранее, является тот факт, что вы можете сопоставлять паттерны в классах случаев. Причина этого в том, что вы получаете метод unapply, который позволяет деконструировать класс case для извлечения его полей.


По сути, то, что вы получаете от Scala при создании класса case (или объекта case, если ваш класс не принимает аргументов), - это объект-одиночка, который служит этой цели как factory и экстрактор .

6 голосов
/ 02 ноября 2017

Помимо того, что люди уже сказали, есть еще несколько основных различий между class и case class

1. Case Class не нуждается в явном new, в то время как класс должен вызываться с new

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

2.By Параметры конструкторов по умолчанию являются приватными в class, а общедоступными - в 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 сравнить себя по значению

// 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
5 голосов
/ 14 февраля 2017

Согласно документации Scala :

Классы Case - это обычные классы:

  • Неизменный по умолчанию
  • Разлагается через сопоставление с образцом
  • Сравнение по структурному равенству, а не по ссылке
  • Кратко, чтобы создать экземпляр и оперировать

Еще одной особенностью ключевого слова case является то, что компилятор автоматически генерирует для нас несколько методов, включая знакомые методы toString, equals и hashCode в Java.

3 голосов
/ 06 мая 2019

Чтобы иметь полное представление о том, что такое кейс-класс:

давайте предположим следующее определение класса дела:

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

и затем выполните следующие действия в терминале:

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

Scala 2.12.8 выведет:

...
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>();
    ()
  }
}
...

Как мы видим, компилятор Scala создает обычный класс Foo и объект-компаньон Foo.

Давайте пройдемся по скомпилированному классу и прокомментируем, что мы получили:

  • внутреннее состояние класса Foo, неизменяемое:
val foo: String
val bar: Int
  • добытчики:
def foo(): String
def bar(): Int
  • методы копирования:
def copy(foo: String, bar: Int): Foo
def copy$default$1(): String
def copy$default$2(): Int
  • реализация scala.Product черта:
override def productPrefix(): String
def productArity(): Int
def productElement(x$1: Int): Object
override def productIterator(): Iterator
  • реализация scala.Equals черты для экземпляров класса make, сравнимых по равенству ==:
def canEqual(x$1: Object): Boolean
override def equals(x$1: Object): Boolean
  • переопределение java.lang.Object.hashCode для выполнения контракта с равным хэш-кодом:
override <synthetic> def hashCode(): Int
  • переопределение java.lang.Object.toString:
override def toString(): String
  • конструктор для реализации по ключевому слову new:
def <init>(foo: String, bar: Int): Foo 

Объект Foo: - метод apply для создания экземпляра без ключевого слова new:

case <synthetic> def apply(foo: String, bar: Int): Foo = new Foo(foo, bar);
  • метод извлечения unupply для использования класса case Foo в сопоставлении с образцом:
case <synthetic> def unapply(x$0: Foo): Option
  • метод защиты объекта как синглтона от десериализации, позволяющий не создавать еще один экземпляр:
<synthetic> private def readResolve(): Object = Foo;
  • объект Foo расширяет scala.runtime.AbstractFunction2 для выполнения такого трюка:
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 от объекта возвращает функцию для создания нового Foo, применяя кортеж из 2 элементов.

Так что класс case - это просто синтаксический сахар.

3 голосов
/ 12 августа 2016

Класс:

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
           ^

Но если мы используем тот же код, но используем case case:

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

Класс человека:

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)

Шаблон соответствия:

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

объект: 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
...