Introduction
Polymorphism is a core concept in object-oriented programming (OOP) that allows objects of different classes to be treated as objects of a common superclass. It enables a single interface to be used for a general class of actions, allowing for code that is more flexible and reusable. Polymorphism can be achieved through inheritance, interfaces, and overriding methods.
Types of Polymorphism
- Compile-time polymorphism (Static polymorphism): Achieved through method overloading.
- Run-time polymorphism (Dynamic polymorphism): Achieved through method overriding.
Compile-time Polymorphism (Method Overloading)
Method overloading is a feature that allows a class to have more than one method with the same name, but with different parameters.
Example
fun main() {
val calculator = Calculator()
println(calculator.add(5, 3)) // Calls add(int, int)
println(calculator.add(5.0, 3.0)) // Calls add(double, double)
println(calculator.add(5, 3, 2)) // Calls add(int, int, int)
}
class Calculator {
// Overloaded methods
fun add(a: Int, b: Int): Int {
return a + b
}
fun add(a: Double, b: Double): Double {
return a + b
}
fun add(a: Int, b: Int, c: Int): Int {
return a + b + c
}
}
Output:
8
8.0
10
Run-time Polymorphism (Method Overriding)
Method overriding occurs when a subclass provides a specific implementation for a method that is already defined in its superclass.
Example
fun main() {
val animal: Animal = Dog()
animal.makeSound() // Calls the overridden method in Dog
val cat: Animal = Cat()
cat.makeSound() // Calls the overridden method in Cat
}
open class Animal {
open fun makeSound() {
println("Animal makes a sound")
}
}
class Dog : Animal() {
override fun makeSound() {
println("Dog barks")
}
}
class Cat : Animal() {
override fun makeSound() {
println("Cat meows")
}
}
Output:
Dog barks
Cat meows
Polymorphism with Interfaces
Polymorphism can also be achieved using interfaces. A class can implement multiple interfaces, and the same interface can be used by different classes.
Example
fun main() {
val dog: Animal = Dog()
dog.makeSound()
val bird: Animal = Bird()
bird.makeSound()
}
interface Animal {
fun makeSound()
}
class Dog : Animal {
override fun makeSound() {
println("Dog barks")
}
}
class Bird : Animal {
override fun makeSound() {
println("Bird chirps")
}
}
Output:
Dog barks
Bird chirps
Example Program with Polymorphism
Here is an example program that demonstrates various aspects of polymorphism in Kotlin:
fun main() {
// Using method overloading
val calculator = Calculator()
println(calculator.add(5, 3)) // Calls add(int, int)
println(calculator.add(5.0, 3.0)) // Calls add(double, double)
println(calculator.add(5, 3, 2)) // Calls add(int, int, int)
// Using method overriding
val animal: Animal = Dog()
animal.makeSound() // Calls the overridden method in Dog
val cat: Animal = Cat()
cat.makeSound() // Calls the overridden method in Cat
// Using interfaces for polymorphism
val dog: AnimalInterface = DogInterface()
dog.makeSound()
val bird: AnimalInterface = BirdInterface()
bird.makeSound()
}
// Method overloading
class Calculator {
fun add(a: Int, b: Int): Int {
return a + b
}
fun add(a: Double, b: Double): Double {
return a + b
}
fun add(a: Int, b: Int, c: Int): Int {
return a + b + c
}
}
// Method overriding
open class Animal {
open fun makeSound() {
println("Animal makes a sound")
}
}
class Dog : Animal() {
override fun makeSound() {
println("Dog barks")
}
}
class Cat : Animal() {
override fun makeSound() {
println("Cat meows")
}
}
// Interface for polymorphism
interface AnimalInterface {
fun makeSound()
}
class DogInterface : AnimalInterface {
override fun makeSound() {
println("Dog barks")
}
}
class BirdInterface : AnimalInterface {
override fun makeSound() {
println("Bird chirps")
}
}
Output:
8
8.0
10
Dog barks
Cat meows
Dog barks
Bird chirps
Conclusion
In this chapter, you learned about polymorphism in Kotlin, including how to achieve compile-time polymorphism using method overloading and run-time polymorphism using method overriding. You also learned how to use interfaces for polymorphism. Polymorphism allows for more flexible and reusable code by enabling a single interface to represent different types of actions. Understanding and applying polymorphism is crucial for writing robust and maintainable Kotlin programs.