Scala Classes, Objects, and Companion Objects

TT

Scala Classes, Objects, and Companion Objects

Scala's object model is built on top of the JVM class system but extends it significantly. You get all the power of Java classes, plus Scala-specific features like companion objects, the apply method, and clean constructor syntax. This module covers how to define and use classes, singleton objects, and companion objects in Scala.

Defining Classes in Scala

In Scala, a class is defined with the class keyword. Parameters in the class header become the primary constructor:

scala
class Person(val name: String, val age: Int) {
  def greet(): String = s"Hi, I'm $name and I'm $age years old."
}

val person = new Person("Alice", 30)
println(person.greet())  // Hi, I'm Alice and I'm 30 years old.
println(person.name)     // Alice

The val keyword in the parameter list makes the field publicly readable. Using var makes it mutable. Omitting both makes the parameter private to the class body.

Adding Methods and Fields

scala
class BankAccount(val owner: String, private var balance: Double) {

  def deposit(amount: Double): Unit = {
    require(amount > 0, "Amount must be positive")
    balance += amount
  }

  def withdraw(amount: Double): Unit = {
    require(amount <= balance, "Insufficient funds")
    balance -= amount
  }

  def getBalance: Double = balance

  override def toString: String = s"BankAccount($owner, $balance)"
}

Key points:

  • private var balance — private mutable field
  • require — throws IllegalArgumentException if condition is false
  • override def toString — overriding a method from a parent class requires override

Visibility Modifiers

Scala provides fine-grained visibility control:

scala
class Employee(val name: String) {
  val publicField = "visible everywhere"
  private val privateField = "visible only in this class"
  protected val protectedField = "visible in this class and subclasses"
  private[this] val instancePrivate = "visible only in this instance"
}

Unlike Java, Scala's private[this] is truly instance-private — not even other instances of the same class can access it.

Singleton Objects

Scala replaces Java's static with singleton objects. An object is a class with exactly one instance, created lazily on first access:

scala
object MathUtils {
  val Pi = 3.14159265358979

  def circleArea(radius: Double): Double = Pi * radius * radius

  def circlePerimeter(radius: Double): Double = 2 * Pi * radius
}

println(MathUtils.Pi)              // 3.14159265358979
println(MathUtils.circleArea(5))   // 78.53981633974483

Objects are useful for utility functions, constants, and factory methods. They are initialized at most once and are thread-safe.

Companion Objects

A companion object shares the same name as a class and lives in the same file. It has access to the class's private members — and the class has access to the object's private members. This is the primary way to implement static-like functionality in Scala:

scala
class Circle(val radius: Double) {
  import Circle._
  def area: Double = Pi * radius * radius
}

object Circle {
  private val Pi = math.Pi

  def apply(radius: Double): Circle = new Circle(radius)

  def fromDiameter(diameter: Double): Circle = new Circle(diameter / 2)
}

// Using apply — no `new` keyword needed
val c1 = Circle(5.0)
val c2 = Circle.fromDiameter(10.0)

The apply Method

When you call Circle(5.0), Scala translates this to Circle.apply(5.0). The apply method is a convention that lets objects be called like functions. This is how Scala collections work — List(1, 2, 3) calls List.apply(1, 2, 3).

scala
object Multiplier {
  def apply(x: Int, y: Int): Int = x * y
}

val result = Multiplier(3, 4)  // 12 — calls Multiplier.apply(3, 4)

The unapply Method

unapply is the inverse of apply — it's used in pattern matching to extract values from an object:

scala
object Email {
  def apply(user: String, domain: String): String = s"$user@$domain"

  def unapply(email: String): Option[(String, String)] = {
    val parts = email.split("@")
    if (parts.length == 2) Some((parts(0), parts(1)))
    else None
  }
}

val email = Email("alice", "example.com")  // "alice@example.com"

email match {
  case Email(user, domain) => println(s"User: $user, Domain: $domain")
  case _ => println("Not an email")
}
// User: alice, Domain: example.com

Inheritance and Abstract Classes

Scala classes can extend other classes:

scala
abstract class Shape {
  def area: Double
  def perimeter: Double
  def describe: String = s"Shape with area ${area} and perimeter ${perimeter}"
}

class Rectangle(val width: Double, val height: Double) extends Shape {
  def area: Double = width * height
  def perimeter: Double = 2 * (width + height)
}

class Circle(val radius: Double) extends Shape {
  def area: Double = math.Pi * radius * radius
  def perimeter: Double = 2 * math.Pi * radius
}

Abstract classes can have both abstract (unimplemented) and concrete (implemented) members. A class can only extend one abstract class.

Final Classes and Methods

Use final to prevent overriding or subclassing:

scala
final class ImmutablePoint(val x: Double, val y: Double)

class Base {
  def canOverride: String = "changeable"
  final def cannotOverride: String = "locked"
}

Frequently Asked Questions

Q: When should I use an object vs a class in Scala? Use a class when you need multiple instances with different state. Use an object when you need exactly one instance — for utility methods, constants, or factory methods. The distinction replaces Java's static keyword entirely.

Q: What's the difference between private and private[this] in Scala? private allows access from any instance of the same class. private[this] is truly instance-private — only the current instance can access the field. In practice, private is used most of the time; private[this] is used when you need strict encapsulation for performance or correctness reasons.

Q: Can a companion object access private members of its companion class? Yes — this is one of the key features of companion objects. The companion object and its class have unrestricted mutual access to each other's private members, which is the standard pattern for implementing factory methods and other class-level functionality.


Part of Scala Mastery Course — Module 6 of 22.