Inheritance and Interfaces

This section documents Kotlin 2.4.x on the JVM, as published at kotlinlang.org, which is the reference these pages are written and verified against.

This content was generated with the assistance of AI and should be verified against kotlinlang.org before being relied on in production.

This section’s bibliography lists the reference material consulted while preparing these pages.

Kotlin’s inheritance model is Java’s, with one inversion (explicit open, covered on Classes and Objects) and one addition with no Java equivalent (class delegation via by).

open and override

A base class — and each member a subclass should be able to override — must be marked open explicitly; overriding itself must be marked override explicitly too, so accidentally overriding (or accidentally failing to override) a base member is a compile error rather than a silent typo:

open class Shape(val name: String) {
    open fun area(): Double = 0.0
    fun describe() = "$name has area ${area()}"   // NOT open -- cannot be overridden
}

class Circle(val radius: Double) : Shape("circle") {
    override fun area(): Double = Math.PI * radius * radius
}

final override fun area() re-seals a member so no further subclass can override it again — the inheritance equivalent of Java’s final on a method, but only meaningful once something is already open.

Abstract Classes

abstract class works as in Java: it cannot be instantiated directly, and its abstract members carry no body — and, notably, an abstract member is implicitly open (there would be no point requiring open on a member with no default implementation to protect):

abstract class Animal {
    abstract fun sound(): String              // no "open" needed -- abstract members already are
    fun describe() = "This animal says: ${sound()}"
}

class Dog : Animal() {
    override fun sound() = "Woof"
}

Interfaces with Default and Property Members

A Kotlin interface can declare a method with a body (a default implementation, like Java’s default methods) and can declare abstract properties — something Java interfaces cannot do at all:

interface Greetable {
    val greeting: String                       // abstract property -- implementer must provide it

    fun greet() = println(greeting)             // default implementation, like Java's "default" methods
}

class English : Greetable {
    override val greeting = "Hello!"            // implemented as a property
}

class Loud : Greetable {
    override val greeting get() = "HELLO!!"     // implemented as a computed property
    override fun greet() = println(greeting.repeat(2))   // overriding the default too
}

Unlike a class, an interface has no state of its own — an interface property must either be abstract (as above) or computed from something else; it cannot hold a backing field.

Class Delegation via by

Kotlin can implement an interface by delegating every member to another object, with one keyword and zero boilerplate forwarding methods — there is no equivalent one-liner in Java, where the same result requires hand-writing every forwarding method:

interface Repository {
    fun findById(id: Int): String?
}

class InMemoryRepository : Repository {
    private val data = mapOf(1 to "Ada", 2 to "Grace")
    override fun findById(id: Int) = data[id]
}

// LoggingRepository implements Repository entirely by forwarding to "delegate" ...
class LoggingRepository(private val delegate: Repository) : Repository by delegate {
    // ...except for findById, which is overridden to add logging around the delegated call
    override fun findById(id: Int): String? {
        println("looking up $id")
        return delegate.findById(id)
    }
}

This is the Decorator pattern with the forwarding boilerplate eliminated by the compiler — by delegate generates an implementation of every interface member that simply calls the same member on delegate, and any member declared explicitly (as findById is above) overrides that generated forwarding.

Kotlin’s Explicit open vs. Java’s Implicit-Open Default

classDiagram class JavaShape { <> +area() double } class JavaCircle { <> +area() double } JavaShape <|-- JavaCircle : subclasses freely —
any non-final class is open by default class KotlinShape { <> +open area() double } class KotlinCircle { <> +override area() double } class KotlinFinalShape { <> +area() double } KotlinShape <|-- KotlinCircle : allowed — base is
explicitly open note for KotlinFinalShape "final by default —
subclassing requires open"

See Also