Skip to content
AgileLogics
5 min read

Objects and Companion Objects

Prerequisites: Case classes

Singleton objects

object defines a class with exactly one instance, created the first time it's used:

object Config:
  val appName = "Inventory"
  val maxItems = 100
  def describe: String = s"$appName (max $maxItems items)"

object MathUtils:
  def clamp(value: Int, low: Int, high: Int): Int =
    value.max(low).min(high)

@main def singletons(): Unit =
  println(Config.describe)
  println(MathUtils.clamp(150, 0, 100))
Inventory (max 100 items)
100

Scala has no static keyword — objects hold what would be static in Java: constants, utility methods, and shared state.

Companion objects

An object with the same name as a class, in the same file, is its companion. They can access each other's private members. Companions are the natural place for factory methods:

case class Email private (value: String)

object Email:
  def parse(raw: String): Option[Email] =
    val cleaned = raw.trim.toLowerCase
    if cleaned.matches("""[^@\s]+@[^@\s]+\.\w+""") then Some(Email(cleaned))
    else None

@main def companions(): Unit =
  println(Email.parse("  Ada@Example.com "))
  println(Email.parse("not-an-email"))
Some(Email(ada@example.com))
None

private on the constructor means the only way to get an Email is through Email.parse — so every Email in the program is valid. This "make invalid states unrepresentable" pattern is common in Scala.

Case objects

A case object is a singleton with a readable toString and pattern matching support — useful for fixed values like commands or states, though Scala 3 enums (later in this level) are usually simpler.