Scala Abstract Classes
An abstract class defines a blueprint that cannot be instantiated directly. It declares some members without implementing them — these are abstract members. Subclasses must provide implementations for all abstract members before they can be instantiated. Abstract classes combine shared behavior with enforced contracts.
Defining an Abstract Class
abstract class Shape(val color: String):
def area(): Double // abstract — no body
def perimeter(): Double // abstract — no body
def describe(): String = // concrete — has a body
f"$color ${getClass.getSimpleName}: area=${area()}%.2f, perimeter=${perimeter()}%.2f"
// val s = Shape("red") // Error: class Shape is abstract; cannot be instantiated
Implementing an Abstract Class
class Circle(color: String, val radius: Double) extends Shape(color):
override def area(): Double = Math.PI * radius * radius
override def perimeter(): Double = 2 * Math.PI * radius
class Rectangle(color: String, val w: Double, val h: Double) extends Shape(color):
override def area(): Double = w * h
override def perimeter(): Double = 2 * (w + h)
class Triangle(color: String, val a: Double, val b: Double, val c: Double)
extends Shape(color):
override def area(): Double =
val s = (a + b + c) / 2
Math.sqrt(s * (s-a) * (s-b) * (s-c)) // Heron's formula
override def perimeter(): Double = a + b + c
val shapes: List[Shape] = List(
Circle("red", 5),
Rectangle("blue", 4, 6),
Triangle("green", 3, 4, 5)
)
shapes.foreach(s => println(s.describe()))
// red Circle: area=78.54, perimeter=31.42
// blue Rectangle: area=24.00, perimeter=20.00
// green Triangle: area=6.00, perimeter=12.00
abstract Shape
/ | \
Circle Rectangle Triangle
area() area() area() ← each provides its own
peri() peri() peri() ← implementation
describe() ← shared (from Shape)
Abstract vals and vars
abstract class Animal:
val name: String // abstract val — no initializer
val sound: String // abstract val
def speak(): Unit = println(s"$name says $sound")
class Dog(val name: String) extends Animal:
val sound: String = "Woof"
class Cat(val name: String) extends Animal:
val sound: String = "Meow"
Dog("Rex").speak() // Rex says Woof
Cat("Luna").speak() // Luna says Meow
Template Method Pattern
Abstract classes excel at defining an algorithm skeleton where subclasses fill in specific steps. This is called the Template Method Pattern:
abstract class DataProcessor:
// Template method — defines the overall flow
def process(data: List[Int]): List[Int] =
val filtered = filter(data)
val sorted = sort(filtered)
val formatted = transform(sorted)
formatted
// Steps — subclasses decide the details
def filter(data: List[Int]): List[Int]
def sort(data: List[Int]): List[Int]
def transform(data: List[Int]): List[Int]
class PositiveEvensProcessor extends DataProcessor:
def filter(data: List[Int]): List[Int] = data.filter(n => n > 0 && n % 2 == 0)
def sort(data: List[Int]): List[Int] = data.sorted
def transform(data: List[Int]): List[Int] = data.map(_ * 10)
val proc = PositiveEvensProcessor()
val input = List(-3, 8, 2, -1, 6, 4, -7, 10)
println(proc.process(input))
// List(20, 40, 60, 80, 100)
Abstract Class vs Trait
Feature Abstract Class Trait
────────────────────── ────────────────────── ─────────────────────────
Constructor params? Yes Yes (Scala 3), No (Scala 2)
Multiple inheritance? No (one class only) Yes (many traits)
Abstract members? Yes Yes
Concrete members? Yes Yes
When to use? Base with constructor Mixin behavior
Single parent hierarchy Multiple parent types
Abstract Class with Constructor Logic
abstract class Investment(val principal: Double, val ratePercent: Double):
require(principal > 0, "Principal must be positive")
require(ratePercent > 0, "Rate must be positive")
val rate: Double = ratePercent / 100
def valueAfter(years: Int): Double // abstract
class SimpleInterest(principal: Double, ratePercent: Double)
extends Investment(principal, ratePercent):
def valueAfter(years: Int): Double =
principal + (principal * rate * years)
class CompoundInterest(principal: Double, ratePercent: Double)
extends Investment(principal, ratePercent):
def valueAfter(years: Int): Double =
principal * Math.pow(1 + rate, years)
val simple = SimpleInterest(10000, 8.0)
val compound = CompoundInterest(10000, 8.0)
for years <- List(1, 5, 10) do
println(f"Year $years: Simple=₹${simple.valueAfter(years)}%,.0f " +
f"Compound=₹${compound.valueAfter(years)}%,.0f")
// Year 1: Simple=₹10,800 Compound=₹10,800
// Year 5: Simple=₹14,000 Compound=₹14,693
// Year 10: Simple=₹18,000 Compound=₹21,589
Partial Implementation
Abstract classes can implement some methods and leave others abstract. Subclasses only need to fill the remaining abstract members:
abstract class Logger:
def format(message: String): String // abstract
def log(message: String): Unit = // concrete — uses format
println(format(message))
def logAll(messages: List[String]): Unit =
messages.foreach(log) // reuses log, which uses format
class TimestampLogger extends Logger:
def format(message: String): String =
s"[${System.currentTimeMillis()}] $message"
class PrefixLogger(prefix: String) extends Logger:
def format(message: String): String = s"[$prefix] $message"
val tLog = TimestampLogger()
val pLog = PrefixLogger("INFO")
tLog.log("Server started") // [1720000000123] Server started
pLog.logAll(List("Step 1", "Step 2"))
// [INFO] Step 1
// [INFO] Step 2
