Case classes differ with ordinary classes in that they
- automatically define
hashCode
,equals
, andtoString
methods - automatically define getter methods for the constructor arguments
- use a compact initialization syntax, e.g.,
Node(Leaf(2), EmptyLeaf)
- decompose them using pattern matching
- have equality comparisons implicitly defined
val
constructor parameters. They are suitable to works with hash maps or sets, since they have a valid, stable hashCode. One can override the default behavior by prepending each constructor argument with var
instead of val
for case classes. In this way, you get the same getter/setter generation just like ordinary classes. However, making case classes mutable causes their equal
and hashCode
methods to be time variant. If an object performs stateful computations on the inside or exhibits other kinds of complex behaviour, it should instantiate an ordinary class instead of a case class. [1]A case class automatically creates a companion object with the same name as the class, which contains
apply
and unapply
methods. The apply
method enables constructing instances without prepending with new
. The unapply
extractor method enables the pattern matching that mentioned in Note I. The compiler optimizes the speed of pattern matching for case classes. [2]
In combination with inheritance, case classes are often used to mirror 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 TreeThat 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)) // 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") }Note that trees construct and deconstruct (through pattern match) with the same syntax, which is also exactly how they are printed (minus spaces).
Technically, one may treat case classes as instances of
Product
and thus inherit these methods:
def productElement(n: Int): Any def productArity: Int def productIterator: Iterator[Any]where the
productArity
returns the number of class parameters, productElement(i)
returns the ith parameter, and productIterator
allows iterating through them.
References
1. Case classes are cool2. Case Classes and Extractors, p.15
No comments:
Post a Comment