← Scala EnglishChapter 08 of 13

Inheritance

## Learning Objectives - Understand class inheritance - Master abstract classes - Work with traits - Learn mixins and linearization - Override methods properly ## Class Inheritance ### Extending a Class ```scala class Animal { def speak(): String = "Some sound" } class Dog extends Animal { override def speak(): String = "Woof!" } class Cat extends Animal { override def speak(): String = "Meow!" } val dog: Animal = new Dog() println(dog.speak()) // Woof! ``` ### Override Modifier The `override` modifier is required when overriding concrete methods: ```scala class Animal { def speak(): String = "..." } class Dog extends Animal { override def speak(): String = "Woof!" // override required } ``` ### Final Members Prevent overriding with `final`: ```scala class Animal { final def speak(): String = "..." } class Dog extends Animal { // Cannot override speak() } ``` ## Abstract Classes ### Defining Abstract Classes ```scala abstract class Shape { def area(): Double // Abstract - no implementation def perimeter(): Double // Abstract def color(): String = "White" // Concrete - with implementation } ``` ### Extending Abstract Classes ```scala class Circle(val radius: Double) extends Shape { def area(): Double = Math.PI * radius * radius def perimeter(): Double = 2 * Math.PI * radius override def color(): String = "Red" } class Rectangle(val width: Double, val height: Double) extends Shape { def area(): Double = width * height def perimeter(): Double = 2 * (width + height) } ``` ## Traits Traits define contracts and can provide default implementations: ```scala trait Printable { def print(): Unit = println(toString()) } trait Serializable { def serialize(): String } ``` ### Mixing in Traits ```scala class Document(val title: String) extends Printable with Serializable { override def toString: String = s"Document($title)" def serialize(): String = title } val doc = new Document("My Doc") doc.print() ``` ### Traits with Abstract Methods ```scala trait Iterator[T] { def hasNext: Boolean def next(): T } class IntIterator(start: Int, end: Int) extends Iterator[Int] { private var current = start def hasNext: Boolean = current < end def next(): Int = { val result = current current += 1 result } } val iter = new IntIterator(1, 5) while (iter.hasNext) { println(iter.next()) } ``` ## Multiple Traits (Mixins) ### Linearization When a class extends multiple traits, Scala uses linearization to resolve method calls: ```scala trait A { def greet(): String = "A" } trait B extends A { override def greet(): String = "B" } trait C extends A { override def greet(): String = "C" } class D extends A with B with C { // Linearization: D -> C -> B -> A -> AnyRef -> Any // greet() resolves to C's implementation } val d = new D() d.greet() // "C" (rightmost wins) ``` ### Mixin Composition ```scala trait Logging { def log(msg: String): Unit = println(s"[LOG] $msg") } trait Timestamping extends Logging { abstract override def log(msg: String): Unit = { super.log(s"${java.time.Instant.now()}: $msg") } } class Service { def process(): Unit = log("Processing") } object Main extends App { val service = new Service with Logging val timestampedService = new Service with Timestamping service.log("Hello") // [LOG] Hello timestampedService.log("Hello") // [LOG] 2024-01-01T12:00:00Z: Hello } ``` ## Constructor Order Constructors execute from parent to child: ```scala class Animal { println("Animal constructor") def speak(): String = "..." } class Dog extends Animal { println("Dog constructor") override def speak(): String = "Woof!" } new Dog() // Output: // Animal constructor // Dog constructor ``` ## Sealed Classes Sealed classes restrict which classes can extend them: ```scala sealed abstract class Result case class Success(value: Int) extends Result case class Failure(message: String) extends Result case object Pending extends Result def describe(result: Result): String = result match { case Success(v) => s"Success: $v" case Failure(msg) => s"Failed: $msg" case Pending => "Pending" } // Cannot add new subclasses outside this file ``` ## Case Class Inheritance ```scala // Inheritance with case classes sealed trait Tree case class Node(left: Tree, right: Tree) extends Tree case class Leaf(value: Int) extends Tree def sum(tree: Tree): Int = tree match { case Node(l, r) => sum(l) + sum(r) case Leaf(v) => v } ``` ## Object Safety ```scala class Base { def method(x: Int): String = x.toString } class Derived extends Base { override def method(x: Int): String = s"($x)" } val b: Base = new Derived() b.method(5) // Calls Derived.method ``` ## Role of AnyRef and Any ```text Any ├── AnyVal (Int, Double, Boolean, etc.) └── AnyRef ├── java.lang.Object (on JVM) ├── User classes └── scala.Null ``` ## Best Practices 1. **Favor composition over inheritance** when possible 2. **Use sealed traits/classes** for exhaustive pattern matching 3. **Use abstract classes** when you need constructor parameters 4. **Use traits** for code reuse across unrelated classes 5. **Keep linearization simple** - avoid deep trait hierarchies ## Summary - Classes extend other classes with `extends` - `override` modifier is required for overriding methods - Abstract classes can have abstract and concrete members - Traits provide interface and default implementation - Linearization determines trait method resolution order - Sealed classes restrict extension to the same file - Use case classes for immutable data structures - Constructors execute from parent to child

Comments

Comments powered by Giscus

To enable comments, add your Giscus embed code here.

Learn more about Giscus →