Classes, objects and companions

Scala · Essentials · 12 min

Classes and objects organise state and behaviour in every Scala codebase.

A class declares its primary constructor in the header: class Account(val owner: String, private var balance: Double). A val parameter becomes a public read-only field, var a mutable one, and a plain parameter is private to the class. The class body runs as part of the constructor, so require(balance >= 0) there validates input. In Scala 3 new is optional: Account("ana", 10) works.

Members are public by default; private and protected restrict them, and private[pkg] opens access to a package. Auxiliary constructors (def this(...)) exist but default parameters usually make them unnecessary.

An object is a singleton: one instance, created lazily on first use. Use it for utilities, constants and entry points.

An object with the same name as a class in the same file is its companion. They can access each other's private members, and the companion is the conventional home for factory methods (apply) and constants. Account.empty("bo") reads better than a constructor, and a private constructor plus a companion apply lets you validate or cache instances.

Methods that take one argument can be written infix (a max b), and symbolic names are allowed (def +(other: Money)), though libraries use them sparingly. override def toString customises printing.

Example

class Account(val owner: String, private var balance: Double):
  require(balance >= 0, "negative balance")
  def deposit(amount: Double): Unit = balance += amount
  def current: Double = balance
  override def toString = s"Account($owner, $balance)"

object Account:
  private var created = 0
  def empty(owner: String): Account =
    created += 1
    Account(owner, 0)
  def count: Int = created

object MathUtil:
  val Tau = 2 * math.Pi
  def clamp(x: Int, lo: Int, hi: Int) = x max lo min hi

@main def demo(): Unit =
  val a = Account.empty("ana")
  a.deposit(25)
  println(a)                          // Account(ana, 25.0)
  println(a.current)                  // 25.0
  println(Account.count)              // 1
  println(MathUtil.clamp(15, 0, 10))  // 10
  // a.balance                        // error: balance is private

Remember

  • Constructor parameters with val/var become fields
  • An object is a lazily created singleton
  • A companion object shares privates with its class and holds factories
  • new is optional in Scala 3

Watch out for

  • Leaving a field as a plain constructor parameter and then trying to read it from outside the class.

Check yourself

  1. class Point(x: Int) declares x without val. Can other code read p.x?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.scala-lang.org/scala3/book/domain-modeling-tools.html