Chapter 12: Inheritance & Abstract Classes
Inheritance & Abstract Classes — this is where Kotlin’s object-oriented power really starts shining! ☕🚀
Inheritance in Kotlin is much safer, cleaner, and more explicit than in Java. You’ll love how Kotlin forces you to think about when and how inheritance should happen.
We’re going to go super slowly, like we’re sitting together in a cozy Mumbai café — I’ll explain every single concept with real-life analogies, complete runnable programs, step-by-step breakdowns, tables, common mistakes with fixes, and plenty of examples you can copy-paste and run right now.
Let’s dive in!
1. open Classes – Inheritance is Opt-In in Kotlin
In Java → all classes are open by default (anyone can inherit). In Kotlin → all classes are final by default (no one can inherit) — you must explicitly mark a class as open if you want to allow inheritance.
Real-life analogy: In Java → every house is built with an open gate (anyone can enter and extend it). In Kotlin → every house has a closed gate — if you want visitors (child classes), you must deliberately put up a sign: “Open for inheritance”.
Syntax:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 |
open class Vehicle { // can be inherited } class Car : Vehicle() { // OK // ... } |
Non-open class (final by default):
|
0 1 2 3 4 5 6 7 8 9 10 |
class FinalClass { // cannot be inherited } // class Child : FinalClass() { } // ERROR! FinalClass is final |
Why is this better?
- Safety — prevents accidental inheritance
- Performance — final classes can be optimized
- Explicit design — you clearly say: “This class is designed to be extended”
2. override Keyword – Mandatory & Explicit
In Kotlin, when you override a method or property from the parent, you must use the override keyword. If you forget it → compile error — no silent bugs!
Example
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
open class Animal { open fun makeSound() { println("Some generic animal sound...") } } class Dog : Animal() { override fun makeSound() { // Must write override! println("Woof Woof! 🐶") } } fun main() { val dog = Dog() dog.makeSound() // Woof Woof! 🐶 } |
Important:
- Parent method/property must be marked open to be overridable
- override is mandatory — no exceptions!
3. Abstract Classes & Abstract Methods
Abstract class → cannot be instantiated directly + can have abstract methods (no body).
Syntax:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
abstract class Shape { abstract fun area(): Double // No body – must be overridden abstract fun perimeter(): Double // Can have concrete methods too fun display() { println("This is a shape") } } |
Concrete subclass must implement all abstract methods
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
class Circle(val radius: Double) : Shape() { override fun area(): Double = Math.PI * radius * radius override fun perimeter(): Double = 2 * Math.PI * radius } class Rectangle(val length: Double, val width: Double) : Shape() { override fun area(): Double = length * width override fun perimeter(): Double = 2 * (length + width) } fun main() { val circle = Circle(5.0) val rect = Rectangle(4.0, 6.0) println("Circle area: {circle.area()}") // ~78.54 println("Rectangle perimeter: ${rect.perimeter()}") // 20.0 // val shape = Shape() // ERROR! Abstract class cannot be instantiated } |
Key differences from Java:
- Abstract class/methods use abstract keyword (not just declaration without body)
- No need to write abstract on class if it has at least one abstract member
4. Calling super – Accessing Parent Implementation
Use super to call parent’s version of method or constructor.
1. Calling parent constructor
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
open class Vehicle(val brand: String) { init { println("Vehicle created: $brand") } } class Car(brand: String, val doors: Int) : Vehicle(brand) { init { println("Car created with $doors doors") } } |
2. Calling parent method
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
open class Animal { open fun makeSound() { println("Generic animal sound") } } class Cat : Animal() { override fun makeSound() { super.makeSound() // Call parent version println("Meow Meow! 🐱") } } |
3. Accessing parent property (rare)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 |
open class Parent { open val message = "Hello from Parent" } class Child : Parent() { override val message = "Hello from Child – ${super.message}" } |
Quick Recap Table (Your Cheat Sheet)
| Feature | Kotlin Way (Best Practice) | Key Point |
|---|---|---|
| Open class | open class Vehicle { … } | Inheritance is opt-in – default is final |
| Override | override fun makeSound() | Mandatory keyword – prevents accidental overrides |
| Abstract class | abstract class Shape { abstract fun area(): Double } | Cannot instantiate – must be extended |
| Abstract method | abstract fun area(): Double | No body – child must implement |
| Call super constructor | : Vehicle(brand) | In primary or secondary constructor |
| Call super method | super.makeSound() | Access parent implementation |
Common Newbie Mistakes & Fixes
| Mistake | Problem | Fix |
|---|---|---|
| Forgetting open on parent class | Child cannot inherit | Add open to class you want to extend |
| Forgetting override keyword | Compile error | Always write override when overriding |
| Trying to instantiate abstract class | Compile error | Use concrete subclass |
| Calling super in wrong place | Wrong constructor called | Call super(…) in secondary constructor |
| Making abstract method with body | Compile error | Remove body or remove abstract |
Homework for You (Let’s Make It Fun!)
- Basic Create open class Animal with open fun makeSound(). Create Dog and Cat that override it.
- Medium Create abstract class Shape with abstract area() and perimeter(). Implement Circle and Rectangle.
- Advanced Create open class Vehicle(val brand: String) with open fun startEngine(). Create Car that calls super.startEngine() and adds its own message.
- Fun Create abstract class Gadget with abstract price(): Double. Create Laptop and Phone with different prices.
- Challenge Fix this buggy code:
Kotlin01234567891011class Parent {fun show() { println("Parent") }}class Child : Parent() {fun show() { println("Child") } // No override!}
You’ve just mastered Kotlin’s inheritance & abstraction — now you can build clean, safe, and powerful class hierarchies!
