Case Classes

Introduction

A Scala case class is like a regular class, with additional functionality built in:

  • Has an apply method that works like a factory method
  • Constructor parameters are public val fields
  • An unapply method makes them easy to use in match expressions
  • A copy method lets you “update while you copy”
  • equals and hashCode methods are generated
    • Lets you compare (==) values, and use in Maps and Sets (sorted), sorting in general
  • A default toString method is generated
  • Similar to:
    • data class in Kotlin
    • Record in Java 14+
    • Struct in C

Case class examples

Some case class examples:

// CREATE
case class Person(firstName: String, lastName: String)

// this uses the APPLY method that is generated for you:
val a = Person("Reginald", "Dwight")

// constructor parameters are VAL fields
a.firstName              // "Reginald"
a.firstName = "Elton"    // error: Reassignment to val firstName

case classes with match expressions:

// step 1: show that these DO NOT work with match
class Dog(val name: String) extends Animal
class Cat(val name: String) extends Animal
// error without `case`: "Dog cannot be used as an extractor 
// in a pattern because it lacks an unapply or unapplySeq method"

// step 2: show that these DO work with match
trait Animal
case class Dog(name: String) extends Animal
case class Cat(name: String) extends Animal
def speak(a: Animal): String = a match
    case Dog(name) => s"Dog ‘$name’ says woof"
    case Cat(name) => s"Cat ‘$name’ says meow"
// usage:
val d = Dog("Rover")
val c = Cat("Morris")
speak(d)
speak(c)

Update as you copy:

val a = Person("Reginald", "Dwight")
a.firstName   // "Reginald"
a.lastName    // "Dwight"

// COPY
val b = a.copy(
    firstName = "Elton",
    lastName = "John"
)
b.firstName   // "Elton"
b.lastName    // "John"

equals and hashCode, Part 1:

val s = scala.collection.SortedSet(10, 4, 8, 2)

class Human(val name: String)      // doesn’t work (plain class)
val a = Human("Alvin")
val b = Human("Alvin")
a == b   // false

case class Human(name: String)     // works!
val a = Human("Alvin")
val b = Human("Alvin")
a == b   // true

equals and hashCode, Part 2:

// FAILS: the resulting Set contains 3 Alvins (plain class)
class Human(val name: String):
    override def toString = name
val s = Set(
    Human("Alvin"),
    Human("Alvin"),
    Human("Alvin"),
)

// WORKS: the resulting Set contains 1 Alvin
case class Human(name: String)
val s = Set(
    Human("Alvin"),
    Human("Alvin"),
    Human("Alvin"),
    Human("Fred"),
    Human("Bert"),
)

Example case class constructors

case class Address(
    street1: String,
    street2: Option[String],
    city: String,
    state: String,
    zipCode: String
)

case class Customer(
    name: String,
    phone: String,
    address: Address
)

case class Order(
    pizzas: Seq[Pizza],
    customer: Customer
)

enum BookFormat:
    case Paperback, Hardcover, Kindle, EPub

case class Book(
    title: String,
    author: String,
    isbn: String,
    publisher: Publisher,
    datePublished: Date,
    numPages: Int,
    formatsAvailable: Seq[BookFormat]
)

More reading