Scala is a hybrid language — everything is an object, and everything is a function. This chapter covers its OOP features in depth.
Classes and Constructors
Scala
class BankAccount(val owner: String, var balance: Double = 0.0) {
// Primary constructor is the class body itself
def deposit(amount: Double): Unit = {
require(amount > 0, "Deposit must be positive")
balance += amount
}
def withdraw(amount: Double): Either[String, Double] = {
if (amount > balance) Left("Insufficient funds")
else {
balance -= amount
Right(balance)
}
}
override def toString: String = s"Account($owner: $$${balance})"
}
val account = new BankAccount("Alice", 1000.0)
account.deposit(500)
account.withdraw(200) // Right(1300.0)
account.withdraw(5000) // Left("Insufficient funds")Inheritance
Scala
abstract class Shape {
def area: Double
def perimeter: Double
def describe: String = f"${getClass.getSimpleName}(area=$area%.2f, perimeter=$perimeter%.2f)"
}
class Circle(val radius: Double) extends Shape {
def area: Double = math.Pi * radius * radius
def perimeter: Double = 2 * math.Pi * radius
}
class Rectangle(val width: Double, val height: Double) extends Shape {
def area: Double = width * height
def perimeter: Double = 2 * (width + height)
}
class Square(side: Double) extends Rectangle(side, side)
val shapes: List[Shape] = List(Circle(5), Rectangle(4, 6), Square(3))
shapes.foreach(s => println(s.describe))
// Circle(area=78.54, perimeter=31.42)
// Rectangle(area=24.00, perimeter=20.00)
// Square(area=9.00, perimeter=12.00)Traits
Scala
// Traits = interfaces + concrete methods + fields
trait Logger {
def log(message: String): Unit = println(s"[LOG] $message")
def logError(message: String): Unit = log(s"ERROR: $message")
}
trait Repository[T] {
def findAll(): List[T]
def findById(id: Long): Option[T]
def save(entity: T): T
def delete(id: Long): Boolean
}
trait Cacheable {
private var cache = Map.empty[String, Any]
def getCached(key: String): Option[Any] = cache.get(key)
def setCache(key: String, value: Any): Unit = cache += (key -> value)
def clearCache(): Unit = cache = Map.empty
}
// Mixing traits
class UserRepository extends Repository[User] with Logger with Cacheable {
def findAll(): List[User] = {
log("Fetching all users")
// implementation...
List.empty
}
// ...
}Companion Objects
Scala
// Singleton object — shared state/methods (like Java static)
class User private (val name: String, val email: String)
object User {
// Factory method
def apply(email: String): User = {
val name = email.split("@").head
new User(name, email)
}
def fromDatabase(id: Long): Option[User] = {
// Database lookup...
Some(new User("Alice", "alice@example.com"))
}
implicit val ordering: Ordering[User] = Ordering.by(_.name)
}
val user = User("alice@example.com") // Uses companion object applyCase Classes (Deep Dive)
Scala
case class Point(x: Double, y: Double)
case class Line(start: Point, end: Point)
// Auto-generated:
// - equals/hashCode (structural equality)
// - toString
// - copy method
// - unapply (for pattern matching)
// - companion object with apply
val p1 = Point(1, 2)
val p2 = Point(1, 2)
p1 == p2 // true (structural equality)
p1 == Point(3, 4) // false
// Copy with modifications
val p3 = p1.copy(x = 10) // Point(10, 2)
// Pattern matching with case classes
def midpoint(line: Line): Point = line match {
case Line(Point(x1, y1), Point(x2, y2)) =>
Point((x1 + x2) / 2, (y1 + y2) / 2)
}Sealed Classes and Enums
Scala
// Sealed — all subclasses must be in the same file
sealed trait PaymentMethod
case class CreditCard(number: String, expiry: String) extends PaymentMethod
case class BankTransfer(account: String, routing: String) extends PaymentMethod
case object Cash extends PaymentMethod
def processPayment(method: PaymentMethod, amount: Double): String = method match {
case CreditCard(number, _) => s"Charged $$${amount} to card ending ${number.takeRight(4)}"
case BankTransfer(account, _) => s"Transferred $$${amount} to account $account"
case Cash => s"Received $$${amount} in cash"
}
// Scala 3 enum
enum Color(val hex: String):
case Red extends Color("#ff0000")
case Green extends Color("#00ff00")
case Blue extends Color("#0000ff")
def name: String = toString.toLowerCase
Color.Red.hex // "#ff0000"💡 Tip: Use traits for capability/behavior, case classes for data, sealed traits for closed hierarchies, and abstract classes when you need constructors.