Sling Academy
Home/Kotlin/Understanding Polymorphism with Interfaces and Abstract Classes in Kotlin

Understanding Polymorphism with Interfaces and Abstract Classes in Kotlin

Last updated: December 05, 2024

Polymorphism is a fundamental concept in object-oriented programming that enables objects to be treated as instances of their parent class. In Kotlin, polymorphism is often achieved through interfaces and abstract classes. This article explores both approaches, providing examples and explanations to help you understand how they work, and why they are powerful tools in your Kotlin development toolkit.

Interfaces in Kotlin

An interface in Kotlin is a blueprint of a class that contains abstract methods, properties, or even concrete functions. Any class that implements an interface must override all its abstract members.

interface Animal {
    fun sound(): String
}

In this simple interface example, Animal declares a single method, sound, which must be implemented by any class that inherits from this interface.

Implementing Interfaces

Here is how you can implement the Animal interface in different classes:

class Dog : Animal {
    override fun sound(): String {
        return "Bark"
    }
}

class Cat : Animal {
    override fun sound(): String {
        return "Meow"
    }
}

Both Dog and Cat classes implement the Animal interface, providing their own definition of the sound method.

Using Interface Instances

With interfaces, you can create a list of Animal objects and iterate over them, even if they point to different concrete implementations.

fun main() {
    val animals: List<Animal> = listOf(Dog(), Cat())
    for (animal in animals) {
        println(animal.sound())
    }
}
// This will output:
// Bark
// Meow

In this case, the animal references are being resolved at runtime, which is a key advantage of polymorphism.

Abstract Classes in Kotlin

Abstract classes in Kotlin can provide a default behavior or state through non-abstract members alongside abstract methods, which need to be overridden.

abstract class Shape {
    abstract fun area(): Double

    fun display() {
        println("The area is: ")
    }
}

This Shape class has one abstract method, area, and one concrete method, display.

Extending Abstract Classes

Now let's implement two different shapes using the Shape abstract class:

class Circle(private val radius: Double) : Shape() {
    override fun area(): Double {
        return Math.PI * radius * radius
    }
}

class Rectangle(private val width: Double, private val height: Double) : Shape() {
    override fun area(): Double {
        return width * height
    }
}

Both Circle and Rectangle provide their own implementation of the abstract area method from Shape.

Utilizing Abstract Classes

This example demonstrates using the abstract class to calculate and display areas of different shapes:

fun main() {
    val shapes: List<Shape> = listOf(Circle(3.0), Rectangle(4.0, 5.0))
    for (shape in shapes) {
        shape.display()
        println(shape.area())
    }
}
// This will output:
// The area is: 
// 28.274333882308138
// The area is: 
// 20.0

The above program demonstrates the polymorphic capabilities of abstract classes, allowing shapes to be processed uniformly.

Interfaces vs Abstract Classes

When to Use Interfaces:

  • When unrelated classes need to share a contract.
  • When a class needs to implement multiple interfaces.

When to Use Abstract Classes:

  • When sharing common behavior or state is necessary.
  • Where an accessor or overriding common code is required.

Kotlin supports multiple interfaces but allows a class to inherit from only one abstract class. Choosing between them often depends on how you conceptualize the design of your application functionalities.

Next Article: Using `is` and Smart Casting for Type Checking in Kotlin

Previous Article: Object Expressions and Anonymous Classes for Dynamic Behavior in Kotlin

Series: Kotlin Object-Oriented Programming

Kotlin

You May Also Like

  • How to Use Modulo for Cyclic Arithmetic in Kotlin
  • Kotlin: Infinite Loop Detected in Code
  • Fixing Kotlin Error: Index Out of Bounds in List Access
  • Setting Up JDBC in a Kotlin Application
  • Creating a File Explorer App with Kotlin
  • How to Work with APIs in Kotlin
  • What is the `when` Expression in Kotlin?
  • Writing a Script to Rename Multiple Files Programmatically in Kotlin
  • Using Safe Calls (`?.`) to Avoid NullPointerExceptions in Kotlin
  • Chaining Safe Calls for Complex Operations in Kotlin
  • Using the Elvis Operator for Default Values in Kotlin
  • Combining Safe Calls and the Elvis Operator in Kotlin
  • When to Avoid the Null Assertion Operator (`!!`) in Kotlin
  • How to Check for Null Values with `if` Statements in Kotlin
  • Using `let` with Nullable Variables for Scoped Operations in Kotlin
  • Kotlin: How to Handle Nulls in Function Parameters
  • Returning Nullable Values from Functions in Kotlin
  • Safely Accessing Properties of Nullable Objects in Kotlin
  • How to Use `is` for Nullable Type Checking in Kotlin