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.