Я искал в Google, чтобы найти различия между case class
и class
. Все упоминают, что, когда вы хотите выполнить сопоставление образцов в классе, используйте класс case. В противном случае используйте классы, а также упомяните о некоторых дополнительных привилегиях, таких как equals и hash code overriding. Но являются ли они единственными причинами, почему следует использовать класс case вместо класса?
Я предполагаю, что для этой функции в Scala должна быть какая-то очень важная причина. Каково объяснение или есть ресурс, чтобы узнать больше о классах классов Scala?
Примеры классов можно рассматривать как простые и неизменные объекты хранения данных, которые должны исключительно зависеть от их аргументов конструктора.
Эта функциональная концепция позволяет нам
Node(1, Leaf(2), None))
)В сочетании с наследованием классы case используются для имитации алгебраических типов данных.
Если объект выполняет вычисления с учетом состояния внутри или проявляет другие виды сложного поведения, он должен быть обычным классом.
Технически, нет разницы между классом и классом 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 =>
}
Обратите внимание, что деревья строят и деконструируют (с помощью сопоставления с шаблоном) с тем же синтаксисом, что также точно так, как они печатаются (минус пробелы).
И они также могут использоваться с хэш-картами или наборами, поскольку они имеют действительный стабильный хэш-код.
(Вы уже упоминали все, кроме последнего).
Это единственные отличия от обычных классов.
Никто не упоминал, что классы case имеют параметры конструктора val
, но это также значение по умолчанию для обычных классов (которое Я считаю несоответствием в дизайне Scala). Дарио подразумевал такое, что отметил, что они "неизменны".
Обратите внимание, что вы можете переопределить значение по умолчанию, добавив аргумент каждого конструктора к var
для классов case. Тем не менее, изменение классов case приводит к тому, что их методы equals
и hashCode
являются вариантами времени. [1]
sepp2k уже упоминал, что классы case автоматически генерируют методы equals
и hashCode
.
Также никто не упоминал, что классы case автоматически создают компаньон object
с тем же именем, что и класс, который содержит методы apply
и unapply
. Метод apply
позволяет создавать экземпляры без добавления с помощью new
. Метод экстрактора unapply
позволяет сопоставить шаблон, о котором говорили другие.
Также компилятор оптимизирует скорость сопоставления шаблонов match
- case
для классов case [2].
Никто не упоминал, что классы case также являются экземплярами Product
и, таким образом, наследуют эти методы:
def productElement(n: Int): Any
def productArity: Int
def productIterator: Iterator[Any]
где productArity
возвращает количество параметров класса, productElement(i)
возвращает параметр я th а productIterator
позволяет выполнять итерацию через них.
Конструкция класса case в Scala также может рассматриваться как удобство для удаления некоторого шаблона.
При построении класса case Scala вы получите следующее.
apply
, который вы можете использовать в качестве метода factory. Преимущество синтаксического сахара в том, что вам не нужно использовать новое ключевое слово.
Поскольку класс неизменен, вы получаете аксессоров, которые являются просто переменными (или свойствами) класса, но без мутаторов (поэтому нет возможности изменять переменные). Параметры конструктора автоматически доступны для вас в качестве общедоступных полей. Гораздо приятнее использовать конструкцию Java bean.
hashCode
, equals
и toString
по умолчанию, а метод equals
сравнивает объект структурно. Создан метод copy
, позволяющий клонировать объект.Самое большое преимущество, о котором говорилось выше, заключается в том, что вы можете сопоставлять шаблон по классам классов. Причина этого заключается в том, что вы получаете метод unapply
, который позволяет вам деконструировать класс case для извлечения его полей.
В сущности, вы получаете от Scala при создании класса case (или объект case, если ваш класс не принимает никаких аргументов) - это одноэлементный объект, который служит цели как factory и как экстрактор.
copy
может изменять поля: val x = y.copy(foo="newValue")
Согласно Scala документация:
Классы классов - это просто регулярные классы, которые:
- Непрерывно по умолчанию
- Разложим через соответствие шаблонов
- По сравнению с структурным равенством вместо ссылки
- Кратко для создания экземпляра и работы
Еще одна особенность ключевого слова case заключается в том, что компилятор автоматически генерирует для нас несколько методов, включая знакомые методы toString, equals и hashCode в Java.
Класс:
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
Класс 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: Классы классов
Никто не упомянул, что объект класса сопутствующего класса имеет 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 элементами), могут быть использованы по вашему выбору.
Помимо того, что люди уже сказали, есть еще несколько принципиальных различий между 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
В отличие от классов, классы case используются только для хранения данных.
Классы классов гибки для приложений, ориентированных на данные, что означает, что вы можете определять поля данных в классе case и определять бизнес-логику в сопутствующем объекте. Таким образом, вы отделяете данные от бизнес-логики.
С помощью метода копирования вы можете наследовать любые или все необходимые свойства из источника и можете изменять их по своему усмотрению.