Scala 的案例类和类有什么区别?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2312881/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-22 01:57:29  来源:igfitidea点击:

What is the difference between Scala's case class and class?

scalafunctional-programmingcase-class

提问by Teja Kantamneni

I searched in Google to find the differences between a case classand a class. Everyone mentions that when you want to do pattern matching on the class, use case class. Otherwise use classes and also mentioning some extra perks like equals and hash code overriding. But are these the only reasons why one should use a case class instead of class?

我在 Google 中搜索以找到 acase class和 a之间的区别class。每个人都提到,当您要对类进行模式匹配时,请使用案例类。否则,请使用类并提及一些额外的好处,例如 equals 和哈希代码覆盖。但是这些是人们应该使用案例类而不是类的唯一原因吗?

I guess there should be some very important reason for this feature in Scala. What is the explanation or is there a resource to learn more about the Scala case classes from?

我想 Scala 中的这个特性应该有一些非常重要的原因。解释是什么,或者是否有资源可以从中了解有关 Scala 案例类的更多信息?

采纳答案by Dario

Case classes can be seen as plain and immutable data-holding objects that should exclusively depend on their constructor arguments.

Case 类可以被看作是简单的和不可变的数据保存对象,它们应该完全依赖于它们的构造函数参数

This functional concept allows us to

这个功能概念使我们能够

  • use a compact initialization syntax (Node(1, Leaf(2), None)))
  • decompose them using pattern matching
  • have equality comparisons implicitly defined
  • 使用紧凑的初始化语法 ( Node(1, Leaf(2), None)))
  • 使用模式匹配分解它们
  • 隐式定义了相等比较

In combination with inheritance, case classes are used to mimic algebraic datatypes.

结合继承,案例类用于模拟代数数据类型

If an object performs stateful computations on the inside or exhibits other kinds of complex behaviour, it should be an ordinary class.

如果一个对象在内部执行有状态计算或表现出其他类型的复杂行为,它应该是一个普通类。

回答by Daniel C. Sobral

Technically, there is no difference between a class and a case class -- even if the compiler does optimize some stuff when using case classes. However, a case class is used to do away with boiler plate for a specific pattern, which is implementing algebraic data types.

从技术上讲,类和案例类之间没有区别——即使编译器在使用案例类时确实优化了一些东西。但是,案例类用于取消特定模式的样板,这是实现代数数据类型

A very simple example of such types are trees. A binary tree, for instance, can be implemented like this:

这种类型的一个非常简单的例子是树。例如,二叉树可以这样实现:

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

That enable us to do the following:

这使我们能够执行以下操作:

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

Note that trees construct and deconstruct (through pattern match) with the same syntax, which is also exactly how they are printed (minus spaces).

请注意,树使用相同的语法构造和解构(通过模式匹配),这也正是它们的打印方式(减去空格)。

And they can also be used with hash maps or sets, since they have a valid, stable hashCode.

它们也可以与哈希映射或集合一起使用,因为它们具有有效、稳定的哈希码。

回答by sepp2k

  • Case classes can be pattern matched
  • Case classes automatically define hashcode and equals
  • Case classes automatically define getter methods for the constructor arguments.
  • 案例类可以模式匹配
  • 案例类自动定义哈希码和等于
  • Case 类自动为构造函数参数定义 getter 方法。

(You already mentioned all but the last one).

(您已经提到了除最后一个之外的所有内容)。

Those are the only differences to regular classes.

这些是与常规课程的唯一区别。

回答by Jean-Philippe Pellet

No one mentioned that case classes are also instances of Productand thus inherit these methods:

没有人提到案例类也是Product这些方法的实例,因此继承了这些方法:

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

where the productArityreturns the number of class parameters, productElement(i)returns the ithparameter, and productIteratorallows iterating through them.

其中productArity返回类参数的数量,productElement(i)返回第i参数,并productIterator允许遍历它们。

回答by Shelby Moore III

No one mentioned that case classes have valconstructor parameters yet this is also the default for regular classes (which I think is an inconsistencyin the design of Scala). Dario implied such where he noted they are "immutable".

没有人提到 case 类有val构造函数参数,但这也是常规类的默认设置(我认为这是Scala 设计中的一种不一致)。达里奥暗示了这种情况,他指出它们是“不可变的”。

Note you can override the default by prepending the each constructor argument with varfor case classes. However, making case classes mutable causes their equalsand hashCodemethods to be time variant.[1]

请注意,您可以通过在每个构造函数参数前面加上varfor case 类来覆盖默认值。然而,使 case 类可变会导致它们的equalshashCode方法随时间变化。 [1]

sepp2kalready mentioned that case classes automatically generate equalsand hashCodemethods.

sepp2k已经提到案例类会自动生成equalshashCode方法。

Also no one mentioned that case classes automatically create a companion objectwith the same name as the class, which contains applyand unapplymethods. The applymethod enables constructing instances without prepending with new. The unapplyextractor method enables the pattern matching that others mentioned.

也没有人提到案例类会自动创建一个object与类同名的伴侣,其中包含applyunapply方法。该apply方法可以在不预先添加 的情况下构建实例new。该unapply提取方法使图案匹配,其他人提及。

Also the compiler optimizes the speed of match-casepattern matching for case classes[2].

还编译器优化的速度match-case对case类模式匹配[2]。

[1] Case Classes Are Cool

[1]案例类很酷

[2] Case Classes and Extractors, pg 15.

[2]案例类和提取器,第 15 页

回答by Faktor 10

The case class construct in Scala can also be seen as a convenience to remove some boilerplate.

Scala 中的 case 类构造也可以看作是删除一些样板的便利。

When constructing a case class Scala gives you the following.

构造案例类时,Scala 为您提供以下内容。

  • It creates a class as well as its companion object
  • Its companion object implements the applymethod that you are able to use as a factory method. You get the syntactic sugar advantage of not having to use the new keyword.
  • 它创建一个类以及它的伴生对象
  • 它的伴随对象实现了apply您可以用作工厂方法的方法。您可以获得不必使用 new 关键字的语法糖优势。

Because the class is immutable you get accessors, which are just the variables (or properties) of the class but no mutators (so no ability to change the variables). The constructor parameters are automatically available to you as public read only fields. Much nicer to use than Java bean construct.

因为类是不可变的,所以你得到了访问器,它只是类的变量(或属性),但没有修改器(因此无法更改变量)。构造函数参数作为公共只读字段自动提供给您。使用起来比 Java bean 构造要好得多。

  • You also get hashCode, equals, and toStringmethods by default and the equalsmethod compares an object structurally. A copymethod is generated to be able to clone an object (with some fields having new values provided to the method).
  • 默认情况下,您还获取hashCodeequalstoString方法,并且该equals方法在结构上比较对象。copy生成一个方法以能够克隆一个对象(某些字段具有提供给该方法的新值)。

The biggest advantage as has been mentioned previously is the fact that you can pattern match on case classes. The reason for this is because you get the unapplymethod which lets you deconstruct a case class to extract its fields.

前面提到的最大优点是您可以对案例类进行模式匹配。这样做的原因是因为您获得了unapply让您解构案例类以提取其字段的方法。



In essence what you are getting from Scala when creating a case class (or a case object if your class takes no arguments) is a singleton object which serves the purpose as a factoryand as an extractor.

从本质上讲,在创建 case 类(或如果您的类不带参数的情况下为 case 对象)时,您从 Scala 获得的是一个单例对象,它用作工厂提取器

回答by DeepakKg

Apart from what people have already said, there are some more basic differences between classand case class

除了人们已经说过的之外,classcase class

1.Case Classdoesn't need explicit new, while class need to be called with new

1.Case Class不需要显式new,而类需要调用new

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

2.By Default constructors parameters are private in class, while its public in case class

2.默认构造函数参数是私有的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 classcompare themselves by value

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

回答by DeepakKg

According to Scala's documentation:

根据 Scala 的文档

Case classes are just regular classes that are:

  • Immutable by default
  • Decomposable through pattern matching
  • Compared by structural equality instead of by reference
  • Succinct to instantiate and operate on

案例类只是常规类,它们是:

  • 默认不可变
  • 可通过模式匹配分解
  • 通过结构相等而不是通过引用进行比较
  • 简洁地实例化和操作

Another feature of the casekeyword is the compiler automatically generates several methods for us, including the familiar toString, equals, and hashCode methods in Java.

case关键字的另一个特点是编译器会自动为我们生成几个方法,包括 Java 中熟悉的 toString、equals 和 hashCode 方法。

回答by user1668782

Class:

班级:

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
           ^

But if we use same code but use case class:

但是如果我们使用相同的代码但用例类:

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

Person class:

人物类:

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)

Pattern Matching:

模式匹配:

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

object: 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

回答by Matthew I.

To have the ultimate understanding of what is a case class:

要最终了解什么是案例类:

let's assume the following case class definition:

让我们假设以下案例类定义:

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

and then do the following in the terminal:

然后在终端中执行以下操作:

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

Scala 2.12.8 will output:

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(): String = Foo.this.foo();

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

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

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

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

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

  <synthetic> def canEqual(x: Object): Boolean = x.$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: Object): Boolean = Foo.this.eq(x).||({
      case <synthetic> val x1: Object = x;
        case5(){
          if (x1.$isInstanceOf[Foo]())
            matchEnd4(true)
          else
            case6()
        };
        case6(){
          matchEnd4(false)
        };
        matchEnd4(x: Boolean){
          x
        }
    }.&&({
      <synthetic> val Foo: Foo = x.$asInstanceOf[Foo]();
      Foo.this.foo().==(Foo.foo()).&&(Foo.this.bar().==(Foo.bar())).&&(Foo.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
val foo: String
val bar: Int
: Foo): Option = if (x
def foo(): String
def bar(): Int
.==(null)) scala.None else new Some(new Tuple2(x
def copy(foo: String, bar: Int): Foo
def copy$default(): String
def copy$default(): Int
.foo(), scala.Int.box(x
override def productPrefix(): String
def productArity(): Int
def productElement(x: Int): Object
override def productIterator(): Iterator
.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>(); () } } ...

As we can see Scala compiler produces a regular class Fooand companion-object Foo.

正如我们所见,Scala 编译器生成了一个常规类Foo和伴随对象Foo

Let's go through the compiled class and comment on what we have got:

让我们通过编译的类并评论我们得到的东西:

  • the internal state of the Fooclass, immutable:
  • 类的内部状态Foo,不可变:
def canEqual(x: Object): Boolean
override def equals(x: Object): Boolean
  • getters:
  • 吸气剂:
override <synthetic> def hashCode(): Int
  • copy methods:
  • 复制方法:
override def toString(): String
  • implementing scala.Producttrait:
  • 实现scala.Product特性:
def <init>(foo: String, bar: Int): Foo 
  • implementing scala.Equalstrait for make case class instances comparable for equality by ==:
  • 通过以下方式实现scala.Equals使案例类实例具有可比性的特征==
case <synthetic> def apply(foo: String, bar: Int): Foo = new Foo(foo, bar);
  • overriding java.lang.Object.hashCodefor obeying the equals-hashcode contract:
  • 重写java.lang.Object.hashCode以遵守 equals-hashcode 合同:
case <synthetic> def unapply(x
<synthetic> private def readResolve(): Object = Foo;
: Foo): Option
  • overriding java.lang.Object.toString:
  • 覆盖java.lang.Object.toString
scala> case class Foo(foo:String, bar: Int)
defined class Foo

scala> Foo.tupled
res1: ((String, Int)) => Foo = scala.Function2$$Lambda4/1935637221@9ab310b
  • constructor for instantiation by newkeyword:
  • 通过new关键字实例化的构造函数:
##代码##

Object Foo: - method applyfor instantiation without newkeyword:

对象 Foo: -apply不带new关键字的实例化方法:

##代码##
  • extractor method unupplyfor using case class Foo in pattern matching:
  • unupply在模式匹配中使用 case 类 Foo 的提取器方法:
##代码##
  • method to protect object as singleton from deserialization for not letting produce one more instance:
  • 保护作为单例的对象免于反序列化的方法,以免再产生一个实例:
##代码##
  • object Foo extends scala.runtime.AbstractFunction2for doing such trick:
  • 对象 Foo 扩展scala.runtime.AbstractFunction2用于做这样的把戏:
##代码##

tupledfrom object returns a funtion to create a new Foo by applying a tuple of 2 elements.

tupledfrom 对象返回一个函数,通过应用 2 个元素的元组来创建一个新的 Foo。

So case class is just syntactic sugar.

所以案例类只是语法糖。