Scala Classes

A class is a blueprint for creating objects. Just as an architect's blueprint defines what a building looks like, a Scala class defines what data an object holds and what it can do. Every object you create from a class is called an instance.

Defining a Basic Class

class Dog(val name: String, val breed: String):
  def bark(): Unit =
    println(s"$name says: Woof!")

  def describe(): String =
    s"$name is a $breed"

Create instances with new (or without new in some Scala 3 contexts):

val dog1 = new Dog("Bruno", "Labrador")
val dog2 = new Dog("Max", "Beagle")

dog1.bark()            // Bruno says: Woof!
println(dog2.describe()) // Max is a Beagle

Class Anatomy Diagram


class Dog ( val name: String , val breed: String ) :
  │           │                  │                 │
  │           └── primary        └── parameter     └── body begins
  │               constructor       with type
  └── keyword

  def bark(): Unit =
    println(...)        ← method defined inside class

Primary Constructor

In Scala, the class header itself is the primary constructor. Parameters listed in the class header automatically become fields when you use val or var:

class Rectangle(val width: Double, val height: Double):
  val area: Double = width * height
  val perimeter: Double = 2 * (width + height)

val r = new Rectangle(5.0, 3.0)
println(r.width)      // 5.0
println(r.area)       // 15.0
println(r.perimeter)  // 16.0

val vs var in Constructor Parameters

class Counter(var count: Int):
  def increment(): Unit = count += 1
  def reset(): Unit = count = 0

val c = new Counter(0)
c.increment()
c.increment()
println(c.count)   // 2
c.reset()
println(c.count)   // 0

val field  →  read-only from outside, set once at construction
var field  →  readable and writable from outside
no modifier →  private to the class, not accessible from outside

Private Fields and Encapsulation

Encapsulation means hiding internal details and exposing only what is necessary. Mark fields private to prevent outside code from reading or modifying them directly:

class BankAccount(private var balance: Double):
  def deposit(amount: Double): Unit =
    if amount > 0 then balance += amount

  def withdraw(amount: Double): Boolean =
    if amount > 0 && amount <= balance then
      balance -= amount
      true
    else
      false

  def getBalance: Double = balance

val account = new BankAccount(1000.0)
account.deposit(500.0)
println(account.getBalance)     // 1500.0
account.withdraw(200.0)
println(account.getBalance)     // 1300.0
// account.balance = 9999.0     // Error: balance is private

Methods with Multiple Parameters

class Calculator:
  def add(a: Double, b: Double): Double = a + b
  def subtract(a: Double, b: Double): Double = a - b
  def power(base: Double, exp: Int): Double =
    Math.pow(base, exp)

val calc = new Calculator()
println(calc.add(3.5, 2.5))       // 6.0
println(calc.power(2.0, 10))      // 1024.0

Auxiliary Constructors

You can provide additional constructors (called auxiliary constructors) using def this(). They must call the primary constructor first:

class Person(val name: String, val age: Int, val city: String):
  // Auxiliary constructor — city defaults to "Unknown"
  def this(name: String, age: Int) = this(name, age, "Unknown")
  // Auxiliary constructor — only name provided
  def this(name: String) = this(name, 0, "Unknown")

val p1 = new Person("Aarav", 30, "Pune")
val p2 = new Person("Diya", 25)        // city = "Unknown"
val p3 = new Person("Karan")           // age = 0, city = "Unknown"

println(p1.city)   // Pune
println(p2.city)   // Unknown

The toString Method

By default, printing an object shows its memory address — not useful. Override toString to control what appears when you print an object:

class Book(val title: String, val author: String, val pages: Int):
  override def toString: String =
    s"'$title' by $author ($pages pages)"

val b = new Book("Scala Programming", "Martin Odersky", 852)
println(b)   // 'Scala Programming' by Martin Odersky (852 pages)

Companion Object Pattern Preview

A common Scala pattern pairs each class with a companion object of the same name. The companion object holds factory methods — functions that create instances of the class. This removes the need to use new:

class Circle(val radius: Double):
  val area: Double = Math.PI * radius * radius

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

val c = Circle(5.0)   // no 'new' needed
println(c.area)       // 78.53...

Class Hierarchy with Inheritance Preview


        Animal
       /      \
    Dog        Cat
   /   \
 Poodle  Labrador
class Animal(val name: String):
  def sound(): String = "..."

class Dog(name: String) extends Animal(name):
  override def sound(): String = "Woof"

class Cat(name: String) extends Animal(name):
  override def sound(): String = "Meow"

val animals: List[Animal] = List(new Dog("Rex"), new Cat("Luna"))
animals.foreach(a => println(s"${a.name}: ${a.sound()}"))
// Rex: Woof
// Luna: Meow

Common Class Patterns

Immutable Data Class

class Point(val x: Double, val y: Double):
  def distanceTo(other: Point): Double =
    val dx = x - other.x
    val dy = y - other.y
    Math.sqrt(dx * dx + dy * dy)

  override def toString: String = s"Point($x, $y)"

val origin = new Point(0.0, 0.0)
val p = new Point(3.0, 4.0)
println(origin.distanceTo(p))   // 5.0

Builder-Style Class with var

class QueryBuilder:
  private var table: String = ""
  private var conditions: List[String] = List()

  def from(t: String): QueryBuilder =
    table = t
    this   // return 'this' for chaining

  def where(condition: String): QueryBuilder =
    conditions = conditions :+ condition
    this

  def build(): String =
    val whereClause = if conditions.isEmpty then "" else " WHERE " + conditions.mkString(" AND ")
    s"SELECT * FROM $table$whereClause"

val query = QueryBuilder()
  .from("users")
  .where("age > 18")
  .where("active = true")
  .build()

println(query)
// SELECT * FROM users WHERE age > 18 AND active = true

When to Use a Class

Use a class when you need to model an entity with both data (fields) and behavior (methods). Use a case class (covered separately) when you primarily need a data container without mutable state. Classes with mutable var fields suit stateful entities like game characters, UI components, or session objects.

Leave a Comment

Your email address will not be published. Required fields are marked *