Scala Inheritance

Inheritance lets a class reuse and extend the behavior of another class. The child class (subclass) inherits all non-private members of the parent class (superclass) and can add new members or override existing ones. Scala uses the extends keyword.

Basic Inheritance

class Animal(val name: String):
  def breathe(): Unit = println(s"$name breathes air")
  def describe(): String = s"I am $name"

class Dog(name: String, val breed: String) extends Animal(name):
  def bark(): Unit = println(s"$name says: Woof!")
  override def describe(): String = s"I am $name, a $breed dog"

class Cat(name: String) extends Animal(name):
  def meow(): Unit = println(s"$name says: Meow!")

val dog = Dog("Bruno", "Labrador")
val cat = Cat("Luna")

dog.breathe()         // Bruno breathes air  (inherited)
dog.bark()            // Bruno says: Woof!   (own method)
println(dog.describe())  // I am Bruno, a Labrador dog  (overridden)

cat.breathe()         // Luna breathes air
cat.meow()            // Luna says: Meow!

         Animal
        /      \
      Dog      Cat
  breathe()  breathe()  ← inherited
  bark()     meow()     ← own
  describe() describe() ← Dog overrides, Cat inherits

The override Keyword

Scala requires the override keyword when redefining a method from the parent. This is a safety feature — if you misspell the method name, the compiler catches it instead of silently creating a new method:

class Shape(val color: String):
  def area(): Double = 0.0
  def describe(): String = s"A $color shape"

class Rectangle(color: String, val width: Double, val height: Double)
    extends Shape(color):
  override def area(): Double = width * height
  override def describe(): String =
    s"A $color rectangle ${width}×${height}, area=${area()}"

val r = Rectangle("red", 4.0, 5.0)
println(r.describe())   // A red rectangle 4.0×5.0, area=20.0
println(r.area())       // 20.0

Calling the Parent with super

class Vehicle(val brand: String):
  def info(): String = s"Brand: $brand"

class Car(brand: String, val model: String) extends Vehicle(brand):
  override def info(): String = super.info() + s", Model: $model"

class ElectricCar(brand: String, model: String, val range: Int)
    extends Car(brand, model):
  override def info(): String = super.info() + s", Range: ${range}km"

val tesla = ElectricCar("Tesla", "Model S", 600)
println(tesla.info())
// Brand: Tesla, Model S, Range: 600km

ElectricCar.info()
  calls super.info()  →  Car.info()
                           calls super.info()  →  Vehicle.info()
                                                  "Brand: Tesla"
                         + ", Model: Model S"
  + ", Range: 600km"
= "Brand: Tesla, Model: Model S, Range: 600km"

Polymorphism

A variable of the parent type can hold any subtype instance. When you call a method on it, Scala runs the actual subtype's version at runtime:

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"

class Cow(name: String) extends Animal(name):
  override def sound(): String = "Moo"

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

final — Preventing Override

class Base:
  final def safeMethod(): String = "Cannot be overridden"
  def normalMethod(): String = "Can be overridden"

class Child extends Base:
  // override def safeMethod() = "..."  // Error: method is final
  override def normalMethod(): String = "Overridden in Child"

Use final on methods whose behavior must never change in subclasses — for example, security-critical operations or algorithm steps that subclasses must not alter.

Inheritance with Abstract Members

abstract class Report(val title: String):
  def generate(): String   // abstract — no body

  def header(): String = s"=== $title ==="     // concrete
  def footer(): String = "=== End of Report ===" // concrete

  def fullReport(): String =
    s"${header()}\n${generate()}\n${footer()}"

class SalesReport(title: String, val total: Double)
    extends Report(title):
  override def generate(): String = f"Total Sales: ₹$total%,.2f"

class InventoryReport(title: String, val items: Int)
    extends Report(title):
  override def generate(): String = s"Items in Stock: $items"

val sales = SalesReport("Q4 Sales", 1250000.0)
println(sales.fullReport())
// === Q4 Sales ===
// Total Sales: ₹12,50,000.00
// === End of Report ===

Constructor Parameters in Inheritance

class Person(val name: String, val age: Int)

// Subclass must pass parent constructor arguments
class Employee(name: String, age: Int, val company: String)
    extends Person(name, age):
  def intro(): String = s"$name, $age, works at $company"

class Manager(name: String, age: Int, company: String, val team: Int)
    extends Employee(name, age, company):
  override def intro(): String =
    super.intro() + s", manages $team people"

val mgr = Manager("Sunita", 42, "TechCorp", 12)
println(mgr.intro())
// Sunita, 42, works at TechCorp, manages 12 people
println(mgr.name)    // Sunita  (from Person)
println(mgr.company) // TechCorp (from Employee)

isInstanceOf and Type Checking

val a: Animal = Dog("Max")

println(a.isInstanceOf[Dog])    // true
println(a.isInstanceOf[Cat])    // false
println(a.isInstanceOf[Animal]) // true

// Safe cast
val dog = a.asInstanceOf[Dog]
dog.bark()   // Max says: Woof!

// Safer: use pattern matching
a match
  case d: Dog => println(s"${d.name} is a dog")
  case c: Cat => println(s"${c.name} is a cat")
  case _      => println("Unknown animal")

Inheritance vs Composition


Inheritance (is-a)              Composition (has-a)
────────────────────────────    ───────────────────────────────
Dog extends Animal              Car has an Engine
Dog IS an Animal                Car HAS an Engine

Use when:                       Use when:
  - Clear is-a relationship       - Has-a relationship
  - Sharing base behavior         - Behavior can vary independently
  - Polymorphic dispatch needed   - Avoid tight coupling

Prefer composition over deep inheritance hierarchies. More than 2–3 levels of inheritance typically signals a design problem. Traits (mixins) offer a flatter alternative for sharing behavior across unrelated classes.

Leave a Comment

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