Kotlin inheritance is a fundamental object-oriented programming concept that allows you to create new classes based on existing ones. In Kotlin inheritance, a child class (subclass or derived class) can inherit properties and functions from a parent class (superclass or base class), enabling code reusability and establishing hierarchical relationships between classes. This comprehensive guide will teach you everything about Kotlin inheritance, from basic syntax to advanced inheritance patterns.
Kotlin inheritance works differently from Java in several key ways. By default, all classes in Kotlin are final, meaning they cannot be inherited unless explicitly marked with the open
keyword. This design choice helps prevent the fragile base class problem and makes your code more robust.
To enable inheritance in Kotlin, you must:
open
keyword:
) to specify inheritanceHere’s the basic structure:
open class ParentClass {
// Parent class properties and methods
}
class ChildClass : ParentClass() {
// Child class specific properties and methods
}
open
Keyword in Kotlin InheritanceThe open
keyword is crucial for Kotlin inheritance. It tells the compiler that a class, property, or function can be inherited or overridden by subclasses.
open class Vehicle {
open val wheels: Int = 4
open fun start() {
println("Vehicle is starting")
}
fun stop() {
println("Vehicle stopped") // This cannot be overridden (not open)
}
}
When working with Kotlin inheritance and primary constructors, the child class must initialize the parent class constructor.
open class Animal(val name: String, val species: String) {
open fun makeSound() {
println("$name makes a sound")
}
}
class Dog(name: String, val breed: String) : Animal(name, "Canine") {
override fun makeSound() {
println("$name barks: Woof! Woof!")
}
fun wagTail() {
println("$name is wagging tail")
}
}
In this example, the Dog
class inherits from Animal
and passes the name
parameter to the parent constructor while adding its own breed
property.
When dealing with secondary constructors in Kotlin inheritance, each secondary constructor must call the parent class constructor using the super
keyword.
open class Electronic(val brand: String) {
var isOn: Boolean = false
constructor(brand: String, model: String) : this(brand) {
println("Electronic device: $brand $model")
}
open fun powerOn() {
isOn = true
println("$brand device is now ON")
}
}
class Smartphone : Electronic {
val operatingSystem: String
constructor(brand: String, os: String) : super(brand) {
this.operatingSystem = os
println("Smartphone created with $os")
}
constructor(brand: String, model: String, os: String) : super(brand, model) {
this.operatingSystem = os
println("Smartphone model: $model with $os")
}
override fun powerOn() {
super.powerOn()
println("Loading $operatingSystem interface...")
}
}
Method overriding is a key feature of Kotlin inheritance that allows child classes to provide specific implementations of parent class methods.
open
override
keywordopen class Shape {
open fun calculateArea(): Double {
return 0.0
}
open fun draw() {
println("Drawing a generic shape")
}
}
class Circle(private val radius: Double) : Shape() {
override fun calculateArea(): Double {
return Math.PI * radius * radius
}
override fun draw() {
println("Drawing a circle with radius $radius")
}
}
class Rectangle(private val width: Double, private val height: Double) : Shape() {
override fun calculateArea(): Double {
return width * height
}
override fun draw() {
super.draw() // Call parent implementation
println("Drawing a rectangle ${width}x${height}")
}
}
Kotlin inheritance allows you to override properties just like methods. You can override val
properties with either val
or var
, but you cannot override var
properties with val
.
open class Employee {
open val department: String = "General"
open val salary: Double = 50000.0
}
class Developer : Employee() {
override val department: String = "Engineering"
override var salary: Double = 80000.0 // val overridden with var
val programmingLanguages = listOf("Kotlin", "Java", "Python")
}
class Manager : Employee() {
override val department: String = "Management"
override val salary: Double = 100000.0
fun conductMeeting() {
println("Conducting team meeting in $department")
}
}
super
Keyword in Kotlin InheritanceThe super
keyword allows you to access parent class members from child classes.
open class GameCharacter(val name: String, var health: Int) {
open fun attack(): Int {
return 10
}
open fun takeDamage(damage: Int) {
health -= damage
println("$name takes $damage damage. Health: $health")
}
}
class Warrior(name: String, health: Int, private val weaponDamage: Int) : GameCharacter(name, health) {
override fun attack(): Int {
val baseDamage = super.attack() // Get parent attack value
return baseDamage + weaponDamage
}
override fun takeDamage(damage: Int) {
val reducedDamage = damage - 5 // Warrior has armor
super.takeDamage(reducedDamage)
}
fun specialAttack(): Int {
println("$name performs a special attack!")
return attack() * 2
}
}
While Kotlin doesn’t support multiple class inheritance, you can implement multiple interfaces to achieve similar functionality.
interface Flyable {
fun fly() {
println("Flying through the air")
}
}
interface Swimmable {
fun swim() {
println("Swimming in water")
}
}
open class Bird(val species: String) {
open fun makeSound() {
println("Bird makes a sound")
}
}
class Duck(species: String) : Bird(species), Flyable, Swimmable {
override fun makeSound() {
println("$species says: Quack!")
}
override fun fly() {
println("$species flies with webbed feet tucked")
}
override fun swim() {
println("$species swims gracefully")
}
}
When implementing multiple interfaces with conflicting method names, you must override the method and specify which implementation to use.
interface A {
fun commonMethod() {
println("Implementation from A")
}
}
interface B {
fun commonMethod() {
println("Implementation from B")
}
}
class ConflictResolver : A, B {
override fun commonMethod() {
super<A>.commonMethod() // Call A's implementation
super<B>.commonMethod() // Call B's implementation
println("Custom implementation in ConflictResolver")
}
}
Abstract classes provide partial implementations and force subclasses to implement abstract members.
abstract class Database {
abstract val connectionString: String
abstract fun connect()
abstract fun disconnect()
// Concrete method available to all subclasses
fun logConnection() {
println("Connecting to: $connectionString")
}
}
class MySQLDatabase(override val connectionString: String) : Database() {
override fun connect() {
logConnection()
println("MySQL connection established")
}
override fun disconnect() {
println("MySQL connection closed")
}
fun executeQuery(query: String) {
println("Executing MySQL query: $query")
}
}
class PostgreSQLDatabase(override val connectionString: String) : Database() {
override fun connect() {
logConnection()
println("PostgreSQL connection established")
}
override fun disconnect() {
println("PostgreSQL connection closed")
}
fun vacuum() {
println("Running PostgreSQL VACUUM")
}
}
Sealed classes provide controlled inheritance where all subclasses are known at compile time.
sealed class NetworkResult<out T> {
data class Success<T>(val data: T) : NetworkResult<T>()
data class Error(val exception: Throwable) : NetworkResult<Nothing>()
data object Loading : NetworkResult<Nothing>()
}
class ApiClient {
fun fetchUserData(userId: String): NetworkResult<User> {
return try {
// Simulate API call
val user = User(userId, "John Doe", "john@example.com")
NetworkResult.Success(user)
} catch (e: Exception) {
NetworkResult.Error(e)
}
}
}
data class User(val id: String, val name: String, val email: String)
fun handleUserData(result: NetworkResult<User>) {
when (result) {
is NetworkResult.Success -> {
println("User loaded: ${result.data.name}")
}
is NetworkResult.Error -> {
println("Error: ${result.exception.message}")
}
is NetworkResult.Loading -> {
println("Loading user data...")
}
}
// No else clause needed - compiler knows all possibilities
}
Let’s create a comprehensive example that demonstrates various Kotlin inheritance concepts:
// Abstract base class
abstract class MediaFile(val name: String, val size: Long) {
abstract fun play()
abstract fun getFileType(): String
fun getFileInfo(): String {
return "File: $name, Size: ${size}MB, Type: ${getFileType()}"
}
open fun compress(): Boolean {
println("Compressing $name...")
return true
}
}
// Interface for downloadable files
interface Downloadable {
val downloadUrl: String
fun download() {
println("Downloading from: $downloadUrl")
}
}
// Interface for streaming
interface Streamable {
fun startStream() {
println("Starting stream...")
}
fun stopStream() {
println("Stopping stream...")
}
}
// Video file implementation
class VideoFile(
name: String,
size: Long,
private val resolution: String,
private val duration: Int,
override val downloadUrl: String
) : MediaFile(name, size), Downloadable, Streamable {
override fun play() {
println("Playing video: $name at $resolution resolution")
println("Duration: $duration minutes")
}
override fun getFileType(): String = "Video"
override fun compress(): Boolean {
println("Compressing video with H.264 codec...")
return super.compress()
}
fun extractThumbnail(): String {
return "${name}_thumbnail.jpg"
}
}
// Audio file implementation
class AudioFile(
name: String,
size: Long,
private val bitrate: Int,
private val artist: String,
override val downloadUrl: String
) : MediaFile(name, size), Downloadable {
override fun play() {
println("Playing audio: $name by $artist")
println("Bitrate: ${bitrate}kbps")
}
override fun getFileType(): String = "Audio"
fun showLyrics() {
println("Displaying lyrics for $name...")
}
}
// Document file (no downloading/streaming)
class DocumentFile(
name: String,
size: Long,
private val pageCount: Int
) : MediaFile(name, size) {
override fun play() {
println("Opening document: $name")
println("Pages: $pageCount")
}
override fun getFileType(): String = "Document"
override fun compress(): Boolean {
println("Compressing document using ZIP algorithm...")
return super.compress()
}
fun convertToPdf(): String {
return "${name.substringBeforeLast('.')}.pdf"
}
}
// Media player class that works with any MediaFile
class MediaPlayer {
fun playFile(file: MediaFile) {
println("\n--- Media Player ---")
println(file.getFileInfo())
file.play()
// Handle downloadable files
if (file is Downloadable) {
file.download()
}
// Handle streamable files
if (file is Streamable) {
file.startStream()
}
file.compress()
}
}
// Usage example
fun main() {
val videoFile = VideoFile(
name = "kotlin_tutorial.mp4",
size = 1024,
resolution = "1080p",
duration = 45,
downloadUrl = "https://example.com/kotlin_tutorial.mp4"
)
val audioFile = AudioFile(
name = "relaxing_music.mp3",
size = 8,
bitrate = 320,
artist = "Nature Sounds",
downloadUrl = "https://example.com/relaxing_music.mp3"
)
val documentFile = DocumentFile(
name = "kotlin_guide.pdf",
size = 2,
pageCount = 150
)
val mediaPlayer = MediaPlayer()
// Play different types of files
mediaPlayer.playFile(videoFile)
mediaPlayer.playFile(audioFile)
mediaPlayer.playFile(documentFile)
// Use specific methods
println("\n--- Specific Operations ---")
println("Video thumbnail: ${videoFile.extractThumbnail()}")
audioFile.showLyrics()
println("PDF conversion: ${documentFile.convertToPdf()}")
}
Expected Output:
--- Media Player ---
File: kotlin_tutorial.mp4, Size: 1024MB, Type: Video
Playing video: kotlin_tutorial.mp4 at 1080p resolution
Duration: 45 minutes
Downloading from: https://example.com/kotlin_tutorial.mp4
Starting stream...
Compressing video with H.264 codec...
Compressing kotlin_tutorial.mp4...
--- Media Player ---
File: relaxing_music.mp3, Size: 8MB, Type: Audio
Playing audio: relaxing_music.mp3 by Nature Sounds
Bitrate: 320kbps
Downloading from: https://example.com/relaxing_music.mp3
Compressing relaxing_music.mp3...
--- Media Player ---
File: kotlin_guide.pdf, Size: 2MB, Type: Document
Opening document: kotlin_guide.pdf
Pages: 150
Compressing document using ZIP algorithm...
Compressing kotlin_guide.pdf...
--- Specific Operations ---
Video thumbnail: kotlin_tutorial.mp4_thumbnail.jpg
Displaying lyrics for relaxing_music.mp3...
PDF conversion: kotlin_guide.pdf
This comprehensive example demonstrates:
MediaFile
Downloadable
and Streamable
super
keyword for calling parent implementationsMediaPlayer
classis
operatoropen
override
keyword for overriding methods and propertiesKotlin inheritance provides powerful tools for creating flexible, maintainable, and type-safe object-oriented code. By understanding these concepts and applying them correctly, you can build robust applications with clean, reusable code structures. For more information about Kotlin inheritance, visit the official Kotlin documentation.