Scala Classes, Objects, and Companion Objects
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:
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) // AliceThe 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
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 fieldrequire— throwsIllegalArgumentExceptionif condition is falseoverride def toString— overriding a method from a parent class requiresoverride
Visibility Modifiers
Scala provides fine-grained visibility control:
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:
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.53981633974483Objects 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:
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).
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:
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.comInheritance and Abstract Classes
Scala classes can extend other classes:
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:
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.
