Image Credits: AI-generated image by freepik.
What is a case class?
Case classes are a special kind of class created using the keyword case. Case classes are excellent for data transfer objects and for representing immutable data. It is a type of class that is mostly utilised for data storage.
case class Person(name: String, age: Int)
The following beneficial features or conveniences are automatically added to our class when the Scala compiler discovers a case class:
- It adds a factory method,
apply()
for creating new instances, so we don’t need to use the keywordnew
to instantiate a class. - Unless all arguments in the parameter list of a case class are declared as
var
, all arguments implicitly get aval
prefix, and thus theval
keyword is optional. To put it another way, by default, case classes automatically transform arguments to value fields (val
fields), therefore theval
keyword is not required to prefix them. If we require a variable field, we can still use thevar
keyword, but this is not what the case class was intended for. - The compiler automatically implements the following methods for the class:
apply
unapply
copy
equals
hashCode
toString
- Every case class has a method named
copy
that allows us to easily create a same or a modified copy of the class’s instance. - A companion object is created automatically with the appropriate
apply
andunapply
methods.
The methods generated by the Scala compiler for case classes aren’t special in any way, other than that they are automatically generated for us. By adding the methods and companion objects ourselves, we may avoid using case classes. Because it would take a lot of time and effort to write each of these methods appropriately for every data-storage class, case classes have the advantage of being more convenient.
Like a regular class, a case class can extend other classes, including trait and case classes. Case classes are Scala’s way of allowing pattern matching on objects without requiring a large amount of boilerplate code.
Let’s explore the advantages of a case class in action:
scala> case class Person(name: String, age: Int)
defined case class Person
scala> val person = Person("John", 36)
val person: Person = Person(John,36)
scala> val otherPerson = person.copy(name = "Robert")
val otherPerson: Person = Person(Robert,36)
scala> val someOtherPerson = person.copy()
val someOtherPerson: Person = Person(John,36)
scala> println(person.equals(someOtherPerson))
true
scala> person == otherPerson
val res0: Boolean = false
scala> person match {
| case Person(x, 36) => s"$x is a younger person"
| case Person(x, 50) => s"$x is a older person"
| }
val res1: String = John is a younger person
- Line 1: Able to instantiate without a new operator because of the companion object’s factory method, Person.apply().
- Line 5: The auto-generated toString method prints the fields in our instance.
- Line 7: The second instance (otherPerson) shares the same value for the second field, so we only need to specify a new value for the first field in the copy method.
- Line 10: Copies of case classes result in strict equivalence. Hence, the equals statement in line 12 results in true.
Case class extends other class: If our case class had extended another class with its own fields but we hadn’t added the fields as case class parameters, the generated methods wouldn’t have been able to make use of them. Before using case classes, it’s necessary to be aware of this important caution.
Copy vs. clone
The case class’s copy
method lets us make a copy
of an object. Remember that a copy
method differs from a clone
method in that a copy
allows us to modify fields at any time while it is being copied.
case class Worker(name: String, department: String)
object CaseClassCopyMethod extends App {
val john = Worker("John", "Sales")
val mike = john.copy(name="Mike")
}
Abstract case class
When we declare an abstract case class, Scala won’t generate the apply method in the companion object, which makes sense as we can’t create an instance of an abstract class.
abstract case class PositiveInt(value: Int)
Case object
We can also create case objects. Just like a regular object, a case object inherits all the features of a regular object. Note that a case object is serialisable by default, whereas a regular object is not.
case object Fruit {
val costPerKg = 10
}
We must extend the Serializable
trait in order to make a case object serialisable.
Comments