Home > Blockchain >  Kotlin Polymorphism Confusion
Kotlin Polymorphism Confusion

Time:02-11

I was following a tutorial for learning kotlin and ran into this example.

open class AquariumPlant(val color: String, private val size: Int)

class GreenLeafyPlant(size: Int) : AquariumPlant("green", size)

fun AquariumPlant.print() = println("AquariumPlant")
fun GreenLeafyPlant.print() = println("GreenLeafyPlant")

val plant = GreenLeafyPlant(size = 10)
plant.print()
println("\n")
val aquariumPlant: AquariumPlant = plant
aquariumPlant.print()  // what will it print?

Well this apparently prints "Aquarium Plant" instead of "GreenLeafyPlant". I was a bit confused by this so I tested this out with this little snippet of code.

open class Aquarium {
    open fun printSize() {
        println("hello")
    }
}

class TowerTank: Aquarium() {
    override fun printSize() {
        println("rawr")
    }
}

fun main() {
    towerTank = TowerTank()
    (towerTank as Aquarium).printSize()
}

So this prints "rawr" and not "hello". My question is why doesn't it print "hello"? Aren't these two examples contradicting themselves? How does the function extensions create this difference in behaviour? Sorry if this may seem like a dumb question, I'm new to Kotlin as you can probably tell.

CodePudding user response:

To understand this we need to understand how extensions work. Extensions don't magically add new members to existing classes. This is technically impossible both in Java and Kotlin. Instead, they work as good old static utility functions in Java. Accessing them as members is just a syntactic sugar.

First example is really similar to these functions:

fun print(plant: AquariumPlant) = println("AquariumPlant")
fun print(plant: GreenLeafyPlant) = println("GreenLeafyPlant")

To make it even more clear, we can rename these functions:

fun printAquariumPlant(plant: AquariumPlant) = println("AquariumPlant")
fun printGreenLeafyPlant(plant: GreenLeafyPlant) = println("GreenLeafyPlant")

Now, it is pretty clear that if we have object like this:

val aquariumPlant: AquariumPlant = GreenLeafyPlant(size = 10)

Then we can only invoke printAquariumPlant() function with it and it will print AquariumPlant, not GreenLeafyPlant. Despite the fact aquariumPlant is actually a GreenLeafyPlant object.

If we move one step back and rename them again to just print, nothing will really change. aquariumPlant variable is of type AquariumPlant (even if it contains GreenLeafyPlant object), so the compiler chooses print(AquariumPlant) function.

This is why we say extensions are resolved statically. Compiler decides which function to call at compile time. Virtual functions are resolved at runtime, taking into consideration the real type of the object.

  • Related