Scala Tutorial

Basics

Control Statements

OOP Concepts

Parameterized - Type

Exceptions

Scala Annotation

Methods

String

Scala Packages

Scala Trait

Collections

Scala Options

Miscellaneous Topics

Scala | Case Class and Case Object

In Scala, case classes and case objects are special constructs that simplify the creation of immutable data structures and provide various automatically derived utilities, making them extremely useful for pattern matching, decomposition, and more.

Let's look at them in more detail:

Case Class

A case class is like a regular class but with some additional benefits:

  1. Immutable by Default: The members are immutable (val) by default.
  2. Factory Method: You don��t need to use the new keyword to create an instance.
  3. Pattern Matching Support: Makes it easy to use in match expressions.
  4. Value-based Equality: Two case classes with the same values are equal.
  5. Automatic toString, hashCode, and equals: Derived automatically.
  6. Unapply Method: Extractor for pattern matching, allowing decomposition.
  7. Copy Method: Allows creating a new instance of the case class with some changed values.
case class Person(name: String, age: Int)

val alice = Person("Alice", 25)
val bob = alice.copy(name = "Bob")

alice match {
  case Person(n, a) => println(s"$n is $a years old")
}

Case Object

A case object is similar to a case class, but it is a singleton, meaning it represents a single instance. It's useful when you want to represent a specific constant value that can be used in pattern matching.

  1. Singleton Pattern: Only one instance is created.
  2. Pattern Matching Support: Just like with case classes.
  3. Value-based Equality: Just like with case classes.
sealed trait Shape
case class Circle(radius: Double) extends Shape
case object Square extends Shape

def shapeInfo(shape: Shape): String = shape match {
  case Circle(r) => s"A circle with radius $r"
  case Square => "A square"
}

Key Notes

  • sealed keyword: When you prefix a trait or class with sealed, it means all of its subclasses must be defined in the same file. This is very useful for pattern matching because the compiler can then check for exhaustiveness.
  • Why case prefix?: The case prefix indicates that various utilities like unapply, copy, etc., are automatically generated by the compiler.

Use case classes when you need a data structure with values (like a tuple but more descriptive) and case objects when you need a single, unique value to represent something. They're a staple in functional programming in Scala, especially when combined with pattern matching.

  1. Introduction to Case Classes and Case Objects in Scala:

    Case classes and case objects are special classes in Scala designed for immutable data modeling. They come with various features like automatic companion objects, pattern matching support, and more.

    case class Person(name: String, age: Int)
    case object EmptyPerson
    
  2. Pattern Matching with Case Classes and Case Objects in Scala:

    Pattern matching is simplified with case classes and case objects.

    def processPerson(person: Person): String = person match {
      case Person("Alice", _) => "Hello, Alice!"
      case Person(_, age) if age < 18 => "You are a minor."
      case _ => "Greetings!"
    }
    
  3. Immutable Data Structures with Case Classes in Scala:

    Case classes are inherently immutable, providing a clean and safe way to represent data.

    val alice = Person("Alice", 25)
    val olderAlice = alice.copy(age = 30)
    

    The copy method creates a new instance with modified fields, preserving immutability.

  4. Case Objects vs Case Classes in Scala:

    Case objects are like case classes but with no companion objects and no constructor parameters. They are often used to represent singletons.

    case class Circle(radius: Double)
    case object EmptyCircle
    

    EmptyCircle is a singleton representing an empty circle.

  5. Deriving Equality and Hash Code with Case Classes in Scala:

    Case classes automatically generate equals and hashCode methods based on their fields.

    val person1 = Person("Alice", 25)
    val person2 = Person("Alice", 25)
    
    println(person1 == person2) // Result: true
    

    The equality check is based on the values of the fields.

  6. Sealed Hierarchies and Case Classes in Scala:

    When used in a sealed hierarchy, case classes enable exhaustive pattern matching.

    sealed trait Shape
    case class Circle(radius: Double) extends Shape
    case class Rectangle(width: Double, height: Double) extends Shape
    
    def area(shape: Shape): Double = shape match {
      case Circle(r) => math.Pi * r * r
      case Rectangle(w, h) => w * h
    }
    

    Adding the sealed modifier ensures that all subclasses are declared in the same file, enabling the compiler to warn if pattern matching is non-exhaustive.