我在谷歌搜索中寻找“case class
”和“class
”之间的区别。大家都提到,当你想对类进行模式匹配时,请使用“case class”。否则请使用类,并提及一些额外的好处,如等式和哈希码重写。但这些是使用“case class”而不是“class”的唯一原因吗?
我猜这个特性在Scala中应该有一些非常重要的理由。有什么解释或有没有资源可以学习更多关于Scala case classes的知识?
我在谷歌搜索中寻找“case class
”和“class
”之间的区别。大家都提到,当你想对类进行模式匹配时,请使用“case class”。否则请使用类,并提及一些额外的好处,如等式和哈希码重写。但这些是使用“case class”而不是“class”的唯一原因吗?
我猜这个特性在Scala中应该有一些非常重要的理由。有什么解释或有没有资源可以学习更多关于Scala case classes的知识?
Case classes 可以被看作是简单且不可变的数据持有对象,应该只依赖于它们的构造函数参数。
这种函数式概念允许我们
Node(1, Leaf(2), None))
)与继承结合使用,case classes 用于模拟代数数据类型 (algebraic datatypes)。
如果一个对象在内部执行状态计算或展示其他形式的复杂行为,则应该是一个普通类。
从技术上讲,类和样例类之间没有区别——即使编译器在使用样例类时进行了一些优化。然而,样例类用于消除特定模式的样板代码,这个模式是实现代数数据类型。
这种类型的一个非常简单的例子是树。例如,二叉树可以像这样实现:
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 =>
}
这些是与普通类的唯一区别。
val
构造函数参数,但这也是常规类的默认值(我认为这是Scala设计上的不一致之处)。Dario暗示了这一点,他指出它们是“不可变的”。var
来覆盖默认设置。然而,使案例类可变会导致它们的equals
和hashCode
方法成为时间变量[1]。
sepp2k已经提到,案例类自动生成equals
和hashCode
方法。object
,其中包含apply
和unapply
方法。 apply
方法使得不需要使用new
即可构造实例。unapply
提取器方法使得其他人提到的模式匹配成为可能。match
-case
模式匹配的速度[2]。没有人提到,case class 也是 Product
的实例,因此继承了这些方法:
def productElement(n: Int): Any
def productArity: Int
def productIterator: Iterator[Any]
productArity
返回类参数的数量,productElement(i)
返回第 ith 个参数,productIterator
允许对它们进行迭代。
apply
方法,您可以将其用作工厂方法。您获得了不必使用new关键字的语法糖优势。hashCode
、equals
和toString
方法。其中equals
方法会对一个对象进行结构性比较,并且会生成一个copy
方法用于克隆对象(提供一些新值给该方法的字段)。unapply
方法,该方法让您可以分解一个case class并提取其字段。copy
方法可以修改字段:val x = y.copy(foo="newValue")
。 - Thilo除了其他人已经提到的内容,class
和case class
之间还有一些基本差异。
1. Case Class
不需要显式使用new
,而class
需要用new
进行调用。
val classInst = new MyClass(...) // For classes
val classInst = MyClass(..) // For 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 class
按值比较自身
// For 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 class有最终的理解:
让我们假设以下的case class定义:
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 class 实例可以通过 ==
进行相等性比较: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
对象 Foo:
- 使用 apply
方法实现无需 new
关键字的实例化:
case <synthetic> def apply(foo: String, bar: Int): Foo = new Foo(foo, bar);
unapply
用于在模式匹配中使用案例类Foo:case <synthetic> def unapply(x$0: Foo): Option
<synthetic> private def readResolve(): Object = Foo;
scala.runtime.AbstractFunction2
是可用于执行此技巧的类,可以通过对象 Foo
进行调用: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。
因此,case 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
^
但是如果我们使用相同的代码,但使用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
人类:
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
对象:单例模式:
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 class Foo(var int: Int)
。[...] 简单且不可变的数据持有对象[...] - jub0bs