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.
