Learn Kotlin Programming(Second Edition)
上QQ阅读APP看书,第一时间看更新

Overriding member extension functions

Member extension functions can be declared as open if you wish to allow them to be overridden in subclasses. In this case, the dispatcher receiver type will be virtual; that is, it will be the runtime instance. However, the extension receiver will always be resolved statically.

To demonstrate this, we will create a class called Element, which has a function called react. This function will accept a Particle type:

open class Particle
class Electron : Particle()

open class Element(val name: String) {

fun react(particle: Particle) {
particle.doReaction(name)
}

open fun Particle.doReaction(name: String) {
println("$name is reacting with a particle")
}

open fun Electron.doReaction(name: String) {
println("$name is reacting with an electron to make an isotope")
}
}

You can see that we have two particles – a superclass and a subclass called Electron. When we call the react function, it just delegates to a member extension function called doReaction. Now, let's use this class as follows:

val selenium = Element("Selenium")
selenium.react(Particle())
selenium.react(Electron())

Creating the element and calling react with both a Particle type and an Element class results in the following:

Selenium is reacting with a particle
Selenium is reacting with a particle

As you can see, the extension function defined on Particle was invoked in both cases, even though, at runtime, one of the inputs was an Electron instance. This is because the extension type of a member extension function is always determined at compile time, and at compile time, the type is Particle.

Now, if we extend Element with a subclass called NobleGas, and then add another react method to accept an Electron subclass instead, we have the following:

class NobleGas(name: String) : Element(name) {

override fun Particle.doReaction(name: String) {
println("$name is noble, it doesn't react with particles")
}

override fun Electron.doReaction(name: String) {
println("$name is noble, it doesn't react with electrons")
}

fun react(particle: Electron) {
particle.doReaction(name)
}
}

To be clear, the react method in this subclass is in addition to the react method inherited from the parent class. The subclass has two functions available, one that accepts any Particle type, and one that specifically accepts an Electron type.

If we now run the following code:

val helium = NobleGas("Helium")
helium.react(Particle())
helium.react(Electron())

The output of this function will differ from the previous example as follows:

Helium is noble, it doesn't react with particles
Helium is noble, it doesn't react with electrons

We have seen in this case that the more specific method is invoked. This is because, at runtime, the more specific react function will be invoked – the one added in the subclass. And, since this function declared the input type to be Electron, the extension function determined at compile time was the extension function defined on the Electron type.