Skip to content

Scala Classes & Objects

Scala is a pure object-oriented language, meaning every value is an object. Classes serve as blueprints for creating these objects, defining their state (fields) and behavior (methods).

A basic class definition includes the class keyword, a name, constructor parameters, and a body containing fields and methods.

// Simple Point class with mutable state
class Point(initialX: Int, initialY: Int) {
// Fields (variables associated with the object)
var x: Int = initialX
var y: Int = initialY
// Method (behavior)
def move(dx: Int, dy: Int): Unit = {
x += dx // Same as x = x + dx
y += dy // Same as y = y + dy
}
// Method to represent the point as a string
override def toString: String = s"Point($x, $y)"
}

Key points:

  • class Point(...): Defines a class named Point.
  • (initialX: Int, initialY: Int): The primary constructor. Its parameters are visible throughout the class body.
  • var x: Int = initialX: Defines a mutable field x initialized with the constructor parameter initialX.
  • def move(...): Defines a method.
  • override def toString...: Overrides the default toString method inherited from AnyRef.

Use the new keyword followed by the class name and constructor arguments to create an instance (object) of the class.

@main def ClassDemo(): Unit = {
// Create an instance of Point
val p1 = new Point(10, 20)
println(s"Initial point: $p1") // Uses the overridden toString method
// Call methods on the object
p1.move(5, -2)
println(s"Moved point: $p1")
// Access fields directly (since they are public by default)
p1.x = 100
println(s"Manually changed point: $p1")
}

Output:

Initial point: Point(10, 20)
Moved point: Point(15, 18)
Manually changed point: Point(100, 18)

Scala provides a concise way to declare constructor parameters also as fields by prefixing them with val (immutable field) or var (mutable field).

// More concise Point class definition
// xc and yc are now public immutable fields
class ConcisePoint(val xc: Int, val yc: Int) {
// We can still add more fields and methods
private var movedCount = 0
def move(dx: Int, dy: Int): Unit = {
// Note: Cannot reassign xc or yc because they are 'val'
// To make them mutable, define as: class ConcisePoint(var xc: Int, var yc: Int)
println(s"Move called, but cannot change immutable Point($xc, $yc)")
movedCount += 1
}
def timesMoved: Int = movedCount
override def toString: String = s"ConcisePoint($xc, $yc)"
}

Scala supports single inheritance using the extends keyword. A subclass inherits non-private members from its superclass.

Key restrictions:

  1. Method overriding requires the override keyword.
  2. Only the primary constructor of the subclass can pass parameters to the superclass constructor.
class Point3D(override val xc: Int, override val yc: Int, val zc: Int)
extends ConcisePoint(xc, yc) { // Pass params to superclass constructor
var z: Int = zc // Additional field
// Override the move method (if it were possible/desired)
// override def move(dx: Int, dy: Int): Unit = { ... }
// Add a new method specific to Point3D
def move(dx: Int, dy: Int, dz: Int): Unit = {
// Since xc, yc are vals from ConcisePoint, we can't change them here either.
// Let's assume ConcisePoint used vars instead for this example to make sense:
// super.move(dx, dy) // Call superclass method if ConcisePoint had mutable x,y
z += dz
println(s"Moved 3D point to roughly ($xc, $yc, $z)") // Accessing inherited xc, yc
}
// Override toString again
override def toString: String = s"Point3D($xc, $yc, $z)"
}
@main def InheritanceDemo(): Unit = {
val p3d = new Point3D(1, 2, 3)
println(p3d)
p3d.move(10, 20, 30) // Calls Point3D's move method
println(p3d)
println(s"Times base class move logic called: ${p3d.timesMoved}") // Access inherited method
}

Output (assuming base class move updates state):

Point3D(1, 2, 3)
Moved 3D point to roughly (1, 2, 33)
Point3D(1, 2, 33)
Times base class move logic called: 0 // Because ConcisePoint.move didn't modify state

Scala does not have static members like Java. Instead, it has singleton objects, defined using the object keyword. An object definition creates a single instance of that type.

Uses:

  • Holding utility methods or constants.
  • Implementing the Singleton design pattern.
  • Creating entry points for applications (@main or def main).
  • Companion objects (see below).
object Logger {
private var level = "INFO"
def setLevel(newLevel: String): Unit = {
level = newLevel
}
def log(message: String): Unit = {
println(s"[$level] $message")
}
}
@main def ObjectDemo(): Unit = {
Logger.log("Application starting...")
Logger.setLevel("DEBUG")
Logger.log("Detailed diagnostic message.")
}

Output:

[INFO] Application starting...
[DEBUG] Detailed diagnostic message.

When a class and a singleton object share the same name and are defined in the same source file, they are called companions. They can access each other’s private members.

Uses:

  • Factory methods for the class (often in the apply method).
  • Holding methods or values related to the class but not specific to an instance (similar to static members in Java).
class Circle(radius: Double) { // Class
// Can access private members of object Circle
def area: Double = Circle.PI * radius * radius
private def secretMethod = "Instance secret"
def revealCompanionSecret = println(Circle.companionSecret)
}
object Circle { // Companion Object
private val PI = 3.14159
private def companionSecret = "Companion secret"
// Factory method using apply
def apply(radius: Double): Circle = new Circle(radius)
// Can access private members of class Circle
def accessInstanceSecret(c: Circle) = println(c.secretMethod)
}
@main def CompanionDemo(): Unit = {
// Use companion object's apply method as factory
val c1 = Circle(5.0)
println(s"Circle area: ${c1.area}")
c1.revealCompanionSecret // Prints "Companion secret"
Circle.accessInstanceSecret(c1) // Prints "Instance secret"
}