Inheritance and Interfaces
Why Kotlin classes are closed to inheritance by default, and how open, override, and interfaces work together.
2 min de lectura
Kotlin supports inheritance, but it makes a deliberate choice that surprises people coming from Java: every class is closed (final) unless you explicitly say otherwise.
Classes are final by default
class Animal(val name: String)
class Dog : Animal("Rex") // Compile error: Animal is final, can't be inherited fromTo allow a class to be subclassed, mark it open:
open class Animal(val name: String) {
open fun makeSound() {
println("$name makes a sound")
}
}
class Dog(name: String) : Animal(name) {
override fun makeSound() {
println("$name barks")
}
}
val dog = Dog("Rex")
dog.makeSound() // Rex barksFunctions follow the same rule: a function must be marked open before a subclass is allowed to override it. This is the opposite default from Java, where every class and method is inheritable unless marked final.
Why closed by default
This is a deliberate design decision, not an oversight. Unrestricted inheritance is one of the most common sources of fragile code: a subclass silently depends on implementation details of a parent class that the parent's author never intended to expose, and a later change to the parent breaks the subclass in ways neither developer anticipated (a problem well-known enough to have a name — the "fragile base class problem"). Kotlin asks class authors to opt in to being extended, deliberately, which tends to produce class hierarchies that were actually designed for inheritance rather than ones where it just happened to be possible.
Calling the parent implementation
super calls the parent class's version of a function that's been overridden:
open class Animal(val name: String) {
open fun makeSound() = println("$name makes a sound")
}
class Dog(name: String) : Animal(name) {
override fun makeSound() {
super.makeSound()
println("$name barks")
}
}Interfaces
An interface declares behavior a class agrees to provide, and — unlike an abstract class — a class can implement any number of them:
interface Drivable {
fun drive()
}
interface Flyable {
fun fly()
}
class FlyingCar : Drivable, Flyable {
override fun drive() = println("Driving on the road")
override fun fly() = println("Taking off")
}
val car = FlyingCar()
car.drive()
car.fly()Kotlin interfaces can also provide default implementations, which a class inherits unless it chooses to override:
interface Greetable {
fun name(): String
fun greet() = println("Hello, ${name()}!") // default implementation
}
class Person(private val personName: String) : Greetable {
override fun name() = personName
}
Person("Alice").greet() // Hello, Alice!Functions on an interface are open for overriding by default — the open/final distinction only applies to classes and to functions defined directly on a class.
Inheritance and interfaces give you the traditional object-oriented toolkit. The next lesson covers a feature with no real equivalent in Java: adding new functions to a class you don't own, without touching its source.