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 →