Polymorphism in Kotlin can be classified into two main types based on when the method to be executed is determined: static (compile-time) polymorphism and runtime (dynamic) polymorphism.
Static polymorphism is resolved during compilation. The compiler determines which method to call based on the method signature, i.e., the number or types of parameters. This is typically achieved through method overloading.
class Calculator {
fun add(a: Int, b: Int) = a + b
fun add(a: Int, b: Int, c: Int) = a + b + c
}
fun main() {
val calc = Calculator()
println(calc.add(2, 3)) // Output: 5
println(calc.add(2, 3, 4)) // Output: 9
}
Explanation: The compiler decides which add method to call based on the number of arguments provided. This is resolved at compile time.
Runtime polymorphism is resolved during program execution. It occurs when a subclass provides its own implementation of a method defined in a parent class or interface. This is achieved through method overriding and interface implementation.
open class Animal {
open fun sound() {
println("Some generic sound")
}
}
class Dog : Animal() {
override fun sound() {
println("Bark")
}
}
fun main() {
val animal: Animal = Dog() // Reference of type Animal, object is Dog
animal.sound() // Output determined at runtime: Bark
}
Explanation: Although the reference type is Animal, the actual object is Dog. The JVM decides at runtime to call Dog's sound() method.
| Aspect | Static (Compile-time) Polymorphism | Runtime (Dynamic) Polymorphism |
|---|---|---|
| Resolution | At compile time | At runtime |
| Achieved By | Method overloading | Method overriding, interface implementation |
| Performance | Faster, no runtime overhead | Slower, runtime dispatch needed |
| Flexibility | Less flexible, fixed at compile time | Highly flexible, dynamic behavior |
| Use Case | Multiple methods with different parameters | Polymorphic behavior for different object types |