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.