Inheritance and interfaces

Kotlin · Practical · 12 min

Interfaces and controlled inheritance structure larger programs and are essential for Java and Android frameworks.

Kotlin classes and members are final by default. To allow subclassing, mark the class open; to allow overriding, mark the member open too. Subclasses use override, which is itself open unless marked final override.

open class Animal(val name: String) { open fun sound() = "..." }
class Dog(name: String) : Animal(name) { override fun sound() = "Woof" }

The superclass constructor is called in the header: : Animal(name). Call the parent implementation with super.sound().

abstract class can't be instantiated and may declare abstract members (implicitly open) that subclasses must implement.

An interface declares abstract members and may provide default implementations; it can declare properties but not store state. A class extends at most one class but implements any number of interfaces, listed after the colon: class Robot : Machine(), Speaker, Walker. If two interfaces provide the same default method, you must override it and can pick one with super<Speaker>.speak().

Type checks: is checks and smart-casts (if (a is Dog) a.fetch()), as casts (throws on failure), as? returns null instead.

Delegation (class Logger(w: Writer) : Writer by w) implements an interface by forwarding to another object, a concise alternative to inheritance (Advanced lesson).

Kotlin's final-by-default design favors composition; frameworks like Spring use the kotlin-spring (all-open) compiler plugin so annotated classes become open automatically.

Example

interface Shape {
    val name: String get() = this::class.simpleName ?: "?"   // default
    fun area(): Double
}

abstract class Polygon(val sides: Int) : Shape

class Square(private val side: Double) : Polygon(4) {
    override fun area() = side * side
}
open class Circle(val r: Double) : Shape {
    override fun area() = Math.PI * r * r
    open fun describe() = "circle r=$r"
}
class Ring(r: Double, private val inner: Double) : Circle(r) {
    override fun area() = super.area() - Math.PI * inner * inner
    override fun describe() = "ring, " + super.describe()
}
fun main() {
    val shapes: List<Shape> = listOf(Square(2.0), Circle(1.0), Ring(2.0, 1.0))
    for (s in shapes) println("${s.name}: ${"%.2f".format(s.area())}")
    // Square: 4.00 / Circle: 3.14 / Ring: 9.42
    val c = shapes[2]
    if (c is Circle) println(c.describe())   // smart cast: ring, circle r=2.0
}

Remember

  • Classes and members are final unless marked open
  • override is required and explicit; call super.member()
  • Interfaces can have default methods but no state
  • is smart-casts; as? casts safely to null

Watch out for

  • Forgetting open on the base class and getting 'This type is final, so it cannot be inherited from'.

Check yourself

  1. Why does class B : A() fail to compile when A is declared class A?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: kotlinlang.org/docs/inheritance.html