В чем разница между классом дел в Scala и классом?

351

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

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

Теги:
functional-programming
case-class

13 ответов

309
Лучший ответ

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

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

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

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

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

  • 0
    @Dario Спасибо за указатели. Значит ADT - это что-то вроде Enums?
  • 9
    @Teja: в некотором роде. ADT являются своего рода параметризованными перечислениями , чрезвычайно мощными и безопасными.
Показать ещё 9 комментариев
134

Технически, нет разницы между классом и классом case, даже если компилятор оптимизирует некоторые вещи при использовании классов 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 =>
}

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

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

48
  • Примеры классов могут быть сопоставлены с образцом
  • Классы классов автоматически определяют хэш-код и равны
  • Классы классов автоматически определяют методы getter для аргументов конструктора.

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

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

  • 12
    Сеттеры не генерируются для классов case, если в аргументе конструктора не указано «var», и в этом случае вы получаете то же поколение getter / setter, что и в обычных классах.
  • 1
    @ Митч: Правда, мой плохой. Исправлено сейчас.
Показать ещё 3 комментария
23

Никто не упоминал, что классы 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] Case Classes and Extractors, стр. 15.

21

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

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

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

  • 1
    Однако они не являются экземплярами Product1, Product2 и т. Д.
8

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

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

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

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

  • Вы также получаете методы hashCode, equals и toString по умолчанию, а метод equals сравнивает объект структурно. Создан метод copy, позволяющий клонировать объект.

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


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

  • 0
    Зачем вам нужна копия неизменяемого объекта?
  • 0
    @ PaŭloEbermann Поскольку метод copy может изменять поля: val x = y.copy(foo="newValue")
3

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

Классы классов - это просто регулярные классы, которые:

  • Непрерывно по умолчанию
  • Разложим через соответствие шаблонов
  • По сравнению с структурным равенством вместо ссылки
  • Кратко для создания экземпляра и работы

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

3

Класс:

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:

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
2

Класс case - это класс, который может использоваться с оператором match/case.

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

Вы видите, что за case следует экземпляр класса Fun, вторым параметром которого является Var. Это очень хороший и мощный синтаксис, но он не может работать с экземплярами какого-либо класса, поэтому есть некоторые ограничения для классов case. И если эти ограничения соблюдаются, можно автоматически определить hashcode и равно.

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

Какие классы классов полезны? Статья Википедии об алгебраических типах данных дает два хороших классических примера, списки и деревья. Поддержка алгебраических типов данных (в том числе знание того, как их сравнивать) является обязательным для любого современного функционального языка.

Какие классы классов не подходят? Некоторые объекты имеют состояние, код типа connection.setConnectTimeout(connectTimeout) не для классов case.

И теперь вы можете читать Прогулка по Scala: Классы классов

2

Никто не упомянул, что объект класса сопутствующего класса имеет tupled defention, который имеет тип:

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

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

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

Вы можете сделать то же самое, без набора, путем создания объекта напрямую, но если ваши наборы данных, выраженные в виде списка кортежей с arity 20 (кортеж с 20 элементами), могут быть использованы по вашему выбору.

0

Помимо того, что люди уже сказали, есть еще несколько принципиальных различий между 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
0

В отличие от классов, классы case используются только для хранения данных.

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

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

0
  • Классы классов определяют объект compagnon с методами apply и unapply
  • Классы классов расширяют Serializable
  • Классы классов определяют equals hashCode и методы копирования
  • Все атрибуты конструктора - val (синтаксический сахар)

Ещё вопросы

Сообщество Overcoder
Наверх
Меню