Scala - Inheritance and Override
• Scala supports single inheritance with the `extends` keyword, allowing classes to inherit fields and methods from a parent class while providing compile-time type safety through its sophisticated...
Key Insights
• Scala supports single inheritance with the extends keyword, allowing classes to inherit fields and methods from a parent class while providing compile-time type safety through its sophisticated type system.
• Method overriding requires the override keyword explicitly, preventing accidental overrides and enabling the compiler to catch errors when attempting to override non-existent methods.
• The combination of abstract classes, traits, and the linearization algorithm provides a powerful framework for building hierarchical type systems with multiple inheritance capabilities through trait composition.
Basic Class Inheritance
Scala inheritance follows the traditional object-oriented model where a subclass extends a parent class using the extends keyword. Unlike Java, Scala requires explicit use of the override keyword when overriding methods.
class Vehicle(val brand: String, val year: Int) {
def describe(): String = s"$brand vehicle from $year"
def startEngine(): Unit = {
println("Engine starting...")
}
}
class Car(brand: String, year: Int, val model: String)
extends Vehicle(brand, year) {
override def describe(): String = {
s"$brand $model from $year"
}
def openTrunk(): Unit = {
println("Trunk opened")
}
}
val myCar = new Car("Toyota", 2023, "Camry")
println(myCar.describe()) // Toyota Camry from 2023
myCar.startEngine() // Engine starting...
myCar.openTrunk() // Trunk opened
The subclass constructor must pass required parameters to the parent class constructor. Notice how brand and year are passed to Vehicle while model is specific to Car.
Override Keyword Requirements
Scala enforces explicit override declarations, preventing bugs that occur in languages where overriding happens implicitly. The compiler will reject code that attempts to override without the keyword or uses the keyword incorrectly.
class Animal {
def makeSound(): String = "Generic sound"
def sleep(): Unit = println("Sleeping")
}
class Dog extends Animal {
// Correct: override keyword present
override def makeSound(): String = "Woof"
// Compiler error: missing override keyword
// def sleep(): Unit = println("Dog sleeping")
override def sleep(): Unit = println("Dog sleeping")
// Compiler error: no such method in parent
// override def fly(): Unit = println("Dogs can't fly")
}
Abstract Classes and Members
Abstract classes cannot be instantiated and may contain abstract members without implementations. Subclasses must provide implementations for all abstract members.
abstract class Shape {
def area(): Double // Abstract method
def perimeter(): Double // Abstract method
def describe(): String = {
s"Shape with area ${area()} and perimeter ${perimeter()}"
}
}
class Rectangle(val width: Double, val height: Double) extends Shape {
override def area(): Double = width * height
override def perimeter(): Double = 2 * (width + height)
}
class Circle(val radius: Double) extends Shape {
override def area(): Double = Math.PI * radius * radius
override def perimeter(): Double = 2 * Math.PI * radius
}
val rect = new Rectangle(5.0, 3.0)
val circle = new Circle(4.0)
println(rect.describe()) // Shape with area 15.0 and perimeter 16.0
println(circle.describe()) // Shape with area 50.26... and perimeter 25.13...
Overriding Fields
Scala allows overriding val and var fields, but with restrictions. A val can override another val or a parameterless def, while var can only override an abstract var.
abstract class Configuration {
val timeout: Int
def maxRetries: Int
}
class ProductionConfig extends Configuration {
override val timeout: Int = 5000
override val maxRetries: Int = 3 // def overridden by val
}
class DevelopmentConfig extends Configuration {
override val timeout: Int = 30000
override val maxRetries: Int = 10
}
// Using lazy val for expensive initialization
abstract class DataSource {
val connectionString: String
}
class DatabaseConnection extends DataSource {
override lazy val connectionString: String = {
println("Initializing connection...")
"jdbc:postgresql://localhost:5432/mydb"
}
}
Super Keyword
The super keyword accesses parent class members, useful when extending functionality rather than completely replacing it.
class Logger {
def log(message: String): Unit = {
println(s"[LOG] $message")
}
}
class TimestampedLogger extends Logger {
override def log(message: String): Unit = {
val timestamp = System.currentTimeMillis()
super.log(s"[$timestamp] $message")
}
}
class DetailedLogger extends TimestampedLogger {
override def log(message: String): Unit = {
super.log(s"[DETAILED] $message")
}
}
val logger = new DetailedLogger()
logger.log("Application started")
// Output: [LOG] [1234567890] [DETAILED] Application started
Sealed Classes and Pattern Matching
Sealed classes restrict inheritance to the same file, enabling exhaustive pattern matching. The compiler can verify all cases are handled.
sealed abstract class Result[+T]
case class Success[T](value: T) extends Result[T]
case class Failure(error: String) extends Result[Nothing]
case object Pending extends Result[Nothing]
def processResult[T](result: Result[T]): String = result match {
case Success(value) => s"Got value: $value"
case Failure(error) => s"Error: $error"
case Pending => "Still processing..."
// Compiler ensures all cases covered
}
val results = List(
Success(42),
Failure("Network timeout"),
Pending
)
results.foreach(r => println(processResult(r)))
Final Classes and Methods
The final keyword prevents further inheritance or overriding, useful for enforcing design constraints.
final class ImmutablePoint(val x: Int, val y: Int) {
// Cannot be extended
}
class BaseService {
final def authenticate(token: String): Boolean = {
// Critical security logic that cannot be overridden
token.length > 10
}
def processRequest(data: String): Unit = {
println(s"Processing: $data")
}
}
class ExtendedService extends BaseService {
// Compiler error: cannot override final method
// override def authenticate(token: String): Boolean = true
override def processRequest(data: String): Unit = {
println("Extended processing")
super.processRequest(data)
}
}
Type Hierarchies and Polymorphism
Scala’s type system enables sophisticated polymorphic designs through inheritance hierarchies.
abstract class Storage[T] {
def save(item: T): Unit
def load(id: String): Option[T]
}
class InMemoryStorage[T] extends Storage[T] {
private var items = Map[String, T]()
override def save(item: T): Unit = {
val id = item.hashCode().toString
items = items + (id -> item)
}
override def load(id: String): Option[T] = items.get(id)
}
class CachedStorage[T](underlying: Storage[T]) extends Storage[T] {
private var cache = Map[String, T]()
override def save(item: T): Unit = {
underlying.save(item)
cache = cache + (item.hashCode().toString -> item)
}
override def load(id: String): Option[T] = {
cache.get(id).orElse {
val result = underlying.load(id)
result.foreach(item => cache = cache + (id -> item))
result
}
}
}
def useStorage[T](storage: Storage[T], item: T): Unit = {
storage.save(item)
println(storage.load(item.hashCode().toString))
}
Constructor Chaining
Scala enforces that auxiliary constructors must call another constructor as their first action, ensuring proper initialization order.
class Employee(val id: Int, val name: String, val department: String) {
private var salary: Double = 0.0
def this(id: Int, name: String) = {
this(id, name, "Unassigned")
}
def this(id: Int, name: String, department: String, salary: Double) = {
this(id, name, department)
this.salary = salary
}
override def toString: String =
s"Employee($id, $name, $department, $$${salary})"
}
class Manager(id: Int, name: String, department: String, val teamSize: Int)
extends Employee(id, name, department) {
def this(id: Int, name: String, teamSize: Int) = {
this(id, name, "Management", teamSize)
}
}
Scala’s inheritance model combines explicit override requirements with flexible abstract classes and sealed hierarchies, providing both safety and expressiveness for building robust type systems.