Inheritance & Interfaces
🧬 Inheritance & Interfaces
🔒 Classes Are final by Default
In Java, every class can be subclassed unless it's explicitly marked final. Kotlin inverts that: every class is implicitly final unless explicitly marked open. This is a deliberate design choice — inheritance is powerful but easy to misuse, so Kotlin makes "can this be subclassed?" an explicit decision rather than an accident.
Both the class (open class Animal) and the specific function being overridden (open fun makeSound()) need the open keyword — a class being open doesn't automatically make its members overridable. And the subclass must use override explicitly; Kotlin never lets an override happen silently.
Inheritance Defaults: Java vs Kotlin
| Java | Kotlin | |
|---|---|---|
| Default | Open (subclassable) | Final (not subclassable) |
| Opt in to closing | final class Foo | class Foo (already final) |
| Opt in to opening | class Foo (already open) | open class Foo |
| Marking an override | @Override (annotation, optional) | override (keyword, required) |
🧩 Abstract Classes
An abstract class can't be instantiated directly, and can declare members with no implementation, which subclasses are forced to provide:
Note that abstract members don't need the open keyword — being abstract already implies they must be overridden. describe() shows a common pattern: a concrete method in the abstract class that relies on the abstract method being implemented by whatever subclass eventually exists.
🔌 Interfaces
A Kotlin interface can declare abstract members (no body) and also provide default implementations, unlike a pre-Java-8 interface:
Multiple Interfaces, One Class
A class can implement any number of interfaces (class Car : Drivable, Insurable), unlike class inheritance, where Kotlin — like Java — allows only one superclass. Interfaces are how Kotlin achieves "multiple inheritance" of behavior.
Interface vs Abstract Class
Use an interface for a capability shared across unrelated classes (Drivable, Comparable). Use an abstract class when subclasses share actual state (constructor properties) and a genuine "is-a" relationship, not just a shared capability.
🔐 Sealed Classes
A sealed class restricts its subclasses to a known, fixed set, all declared in the same file. This is Kotlin's answer to representing "one of these specific things, and nothing else":
Because a sealed class's subclasses are fixed and known, a when expression that covers every branch doesn't need (or allow, in expression form) an else. If a new subclass of NetworkResult is added later and this when isn't updated, the compiler flags it as an error — not a bug discovered at runtime. A plain interface or open class gives no such guarantee, since anyone, anywhere, could add a new implementation.
💻 Coding Challenges
Challenge 1: Open Class & Override
Write an open class Employee(val name: String) with an open fun payDescription() returning a generic string. Create a subclass Manager(name: String, val bonus: Double) : Employee(name) that overrides payDescription() to mention the bonus. Store a Manager in an Employee-typed variable and call payDescription() on it.
Goal: Practice open/override and see polymorphism through a superclass-typed reference.
Challenge 2: Interface with a Default Method
Write an interface Greetable with an abstract property name: String, an abstract function customGreeting(): String, and a default function greet() that prints customGreeting(). Implement it in two different classes with different greetings, and call greet() on each.
Goal: Combine abstract members with a default implementation in one interface.
Challenge 3: Sealed Class with when
Write a sealed class Shape with three subclasses: data class Circle(val radius: Double), data class Square(val side: Double), and data class Rectangle(val width: Double, val height: Double). Write a function area(shape: Shape): Double using a when expression with no else branch that computes the correct area for each.
Goal: Practice sealed classes and see exhaustive when in action.
The NetworkResult example above (Success / Error / Loading) is close to a verbatim preview of how UI state is modeled in real Android apps with Jetpack Compose — a screen's state is almost always a sealed class with exactly this shape. Getting comfortable with sealed classes and exhaustive when now sets up that pattern well ahead of time.
🎯 What's Next
Next chapter: Collections & Functional Operators — List/Set/Map, and the map/filter/reduce/flatMap operators used constantly in idiomatic Kotlin.