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

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

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

Ответ 1

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

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

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

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

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

Ответ 2

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

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

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

Ответ 3

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

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

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

Ответ 4

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

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

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

Ответ 5

Никто не упоминал, что классы 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.

Ответ 6

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

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

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

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

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

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


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

Ответ 7

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

Ответ 8

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

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

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

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

Ответ 9

Класс:

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

scala> val an1 = new Animal("Padddington")
an1: Animal = [email protected]

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

Ответ 10

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

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

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 для создания экземпляров класса case, сравнимых по равенству с помощью ==:
def canEqual(x$1: Object): Boolean
override def equals(x$1: Object): Boolean
  • переопределение java.lang.Object.hashCode для выполнения контракта equals-hashcode:
override <synthetic> def hashCode(): Int
  • переопределение java.lang.Object.toString:
override def toString(): String
  • конструктор для создания экземпляра по new ключевому слову:
def <init>(foo: String, bar: Int): Foo 

Object 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/[email protected]

tupled from object возвращает функцию для создания нового Foo, применяя кортеж из 2 элементов.

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

Ответ 11

Никто не упомянул, что объект класса сопутствующего класса имеет 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 элементами), могут быть использованы по вашему выбору.

Ответ 12

Класс 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: Классы классов

Ответ 13

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

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

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

Ответ 14

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

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

Некоторые разработчики избегают писать классы case из-за дополнительных 20 методов, которые вы можете увидеть, разобрав файл class.

Пожалуйста, обратитесь по этой ссылке, если вы хотите проверить все методы внутри класса case.

Ответ 15

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

Ответ 16

Некоторые из ключевых особенностей case classes перечислены ниже

  1. тематические классы являются неизменяемыми.
  2. Вы можете создавать экземпляры классов дел без ключевого слова new.
  3. case-классы можно сравнивать по значению

Пример кода Scala на скрипке Scala, взятый из документов Scala.

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